J'utilise un int
type pour stocker une valeur. Selon la sémantique du programme, la valeur varie toujours dans une très petite plage (0 - 36), et int
(pas a char
) n'est utilisée qu'en raison de l'efficacité du processeur.
Il semble que de nombreuses optimisations arithmétiques spéciales puissent être effectuées sur une si petite plage d'entiers. De nombreux appels de fonction sur ces nombres entiers peuvent être optimisés en un petit ensemble d'opérations «magiques», et certaines fonctions peuvent même être optimisées dans des recherches de table.
Alors, est-il possible de dire au compilateur que int
c'est toujours dans cette petite plage, et est-il possible pour le compilateur de faire ces optimisations?
unsigned
types car ils sont plus faciles à raisonner pour le compilateur.var value: 0..36;
.int
etunsigned int
doivent également être étendus de 32 à 64 bits sur la plupart des systèmes avec des pointeurs 64 bits. Notez que sur x86-64, les opérations sur les registres 32 bits s'étendent de zéro à 64 bits gratuitement (pas d'extension de signe, mais le débordement signé est un comportement non défini, de sorte que le compilateur peut simplement utiliser des mathématiques signées 64 bits s'il le souhaite). Ainsi, vous ne voyez que des instructions supplémentaires pour étendre les arguments de fonction 32 bits à zéro, pas les résultats du calcul. Vous le feriez pour des types non signés plus étroits.Réponses:
Oui c'est possible. Par exemple, car
gcc
vous pouvez utiliser__builtin_unreachable
pour informer le compilateur des conditions impossibles, comme ceci:Nous pouvons envelopper la condition ci-dessus dans une macro:
Et utilisez-le comme ceci:
Comme vous pouvez le voir ,
gcc
effectue des optimisations en fonction de ces informations:Produit:
Un inconvénient, cependant, que si votre code rompt un jour ces hypothèses, vous obtenez un comportement indéfini .
Il ne vous avertit pas lorsque cela se produit, même dans les versions de débogage. Pour déboguer / tester / attraper les bogues avec des hypothèses plus facilement, vous pouvez utiliser une macro hybride assume / assert (crédits à @David Z), comme celle-ci:
Dans les versions de débogage (
NDEBUG
sans définition), il fonctionne comme unassert
message d'erreur d'impression ordinaire etabort
un programme d'ingénierie, et dans les versions de version, il utilise une hypothèse, produisant un code optimisé.Notez, cependant, qu'il ne remplace pas les versions régulières
assert
-cond
reste dans les versions de version, vous ne devriez donc pas faire quelque chose commeassume(VeryExpensiveComputation())
.la source
return 2
branche a été éliminée du code par le compilateur.__builtin_expect
est un indice non strict.__builtin_expect(e, c)
devrait se lire comme "e
est le plus susceptible d'évaluerc
", et peut être utile pour optimiser la prédiction de branche, mais cela ne se limite pase
à toujoursc
, donc ne permet pas à l'optimiseur de rejeter d'autres cas. Regardez comment les branches sont organisées en assemblage .__builtin_unreachable()
.assert
, par exemple définirassume
commeassert
quandNDEBUG
n'est pas défini, et comme__builtin_unreachable()
quandNDEBUG
est défini. De cette façon, vous bénéficiez de l'hypothèse dans le code de production, mais dans une version de débogage, vous avez toujours une vérification explicite. Bien sûr, vous devez alors faire suffisamment de tests pour vous assurer que l'hypothèse sera satisfaite à l'état sauvage.Il existe un support standard pour cela. Ce que vous devez faire est d'inclure
stdint.h
(cstdint
), puis d'utiliser le typeuint_fast8_t
.Cela indique au compilateur que vous n'utilisez que des nombres compris entre 0 et 255, mais qu'il est libre d'utiliser un type plus grand si cela donne un code plus rapide. De même, le compilateur peut supposer que la variable n'aura jamais une valeur supérieure à 255 et ensuite effectuer des optimisations en conséquence.
la source
uint_fast8_t
est en fait un type 8 bits (par exempleunsigned char
) comme sur x86 / ARM / MIPS / PPC ( godbolt.org/g/KNyc31 ). Au début du DEC Alpha avant 21164A , les chargements / magasins d'octets n'étaient pas pris en charge, donc toute implémentation sensée serait utiliséetypedef uint32_t uint_fast8_t
. AFAIK, il n'y a pas de mécanisme pour qu'un type ait des limites de plage supplémentaires avec la plupart des compilateurs (comme gcc), donc je suis à peu près certainuint_fast8_t
qu'il se comporterait exactement commeunsigned int
ou quoi que ce soit dans ce cas.bool
est spécial, et est limité à 0 ou 1, mais c'est un type intégré, non défini par les fichiers d'en-tête en termes dechar
, sur gcc / clang. Comme je l'ai dit, je ne pense pas que la plupart des compilateurs aient un mécanisme cela rendrait cela possible.)uint_fast8_t
c'est une bonne recommandation, car il utilisera un type 8 bits sur les plates-formes où c'est aussi efficace queunsigned int
. (Je ne suis pas vraiment sûr de ce que lesfast
types sont censés être rapide pour , et si le compromis entre cache l' empreinte est censé faire partie.). x86 a un support étendu pour les opérations d'octet, même pour faire l'ajout d'octets avec une source de mémoire, vous n'avez donc même pas à faire une charge séparée à extension zéro (ce qui est également très bon marché). gcc créeuint_fast16_t
un type 64 bits sur x86, ce qui est insensé pour la plupart des utilisations (contre 32 bits). godbolt.org/g/Rmq5bv .La réponse actuelle est bonne pour le cas où vous savez avec certitude quelle est la plage, mais si vous voulez toujours un comportement correct lorsque la valeur est en dehors de la plage attendue, cela ne fonctionnera pas.
Dans ce cas, j'ai trouvé que cette technique peut fonctionner:
L'idée est un compromis code-données: vous déplacez 1 bit de données (que ce soit
x == c
) dans la logique de contrôle .Cela fait allusion à l'optimiseur qui
x
est en fait une constante connuec
, l'encourageant à intégrer et à optimiser le premier appel defoo
séparément du reste, peut-être assez lourdement.Assurez-vous de factoriser le code en un seul sous
foo
- programme , cependant - ne dupliquez pas le code.Exemple:
Pour que cette technique fonctionne, vous devez être un peu chanceux - il y a des cas où le compilateur décide de ne pas évaluer les choses de manière statique, et ils sont plutôt arbitraires. Mais quand ça marche, ça marche bien:
Utilisez simplement
-O3
et notez les constantes pré-évaluées0x20
et0x30e
dans la sortie de l'assembleur .la source
if (x==c) foo(c) else foo(x)
? Si seulement pour attraper lesconstexpr
implémentations defoo
?constexpr
était une chose et je n'ai jamais pris la peine de la «mettre à jour» par la suite (même si je n'ai jamais vraiment pris la peine de m'inquiéter,constexpr
même après), mais la raison pour laquelle je ne l'ai pas fait au départ était que je voulais faciliter la tâche du compilateur pour les factoriser en tant que code commun et supprimer la branche s'il décidait de les laisser comme des appels de méthode normaux et de ne pas les optimiser. Je m'attendais à ce que si j'introduisais,c
il soit vraiment difficile pour le compilateur de c (désolé, mauvaise blague) que les deux soient le même code, bien que je ne l'ai jamais vérifié.Je veux juste dire que si vous voulez une solution plus standard en C ++, vous pouvez utiliser l'
[[noreturn]]
attribut pour écrire la vôtreunreachable
.Je vais donc réutiliser l' excellent exemple de deniss pour démontrer:
Ce qui, comme vous pouvez le voir , donne un code presque identique:
L'inconvénient est bien sûr que vous recevez un avertissement indiquant qu'une
[[noreturn]]
fonction retourne effectivement.la source
clang
, quand ma solution originale ne fonctionne pas , donc une belle astuce et +1. Mais tout cela dépend beaucoup du compilateur (comme Peter Cordes nous l'a montré,icc
cela peut aggraver les performances), donc ce n'est toujours pas universellement applicable. Remarque mineure également: launreachable
définition doit être disponible pour l'optimiseur et intégrée pour que cela fonctionne .