Je suis tombé sur un #define
dans lequel ils utilisent __builtin_expect
.
La documentation dit:
Fonction intégrée:
long __builtin_expect (long exp, long c)
Vous pouvez utiliser
__builtin_expect
pour fournir au compilateur des informations de prédiction de branche. En général, vous devriez préférer utiliser les commentaires de profil réels pour cela (-fprofile-arcs
), car les programmeurs sont notoirement mauvais pour prédire les performances réelles de leurs programmes. Cependant, il existe des applications dans lesquelles ces données sont difficiles à collecter.La valeur de retour est la valeur de
exp
, qui doit être une expression intégrale. La sémantique du intégré est que cela est attenduexp == c
. Par exemple:if (__builtin_expect (x, 0)) foo ();
indiquerait que nous ne nous attendons pas à appeler
foo
, puisque nous nous attendonsx
à être à zéro.
Alors pourquoi ne pas utiliser directement:
if (x)
foo ();
au lieu de la syntaxe compliquée avec __builtin_expect
?
if ( x == 0) {} else foo();
.. ou tout simplementif ( x != 0 ) foo();
qui est équivalent au code de la documentation GCC.Réponses:
Imaginez le code d'assemblage qui serait généré à partir de:
Je suppose que ça devrait être quelque chose comme:
Vous pouvez voir que les instructions sont disposées dans un ordre tel que le
bar
cas précède lefoo
cas (par opposition au code C). Cela peut mieux utiliser le pipeline du processeur, car un saut écrase les instructions déjà extraites.Avant que le saut ne soit exécuté, les instructions en dessous (le
bar
cas) sont poussées vers le pipeline. Comme lefoo
cas est peu probable, sauter aussi est peu probable, il est donc peu probable que le pipeline soit écrasé.la source
x = 0
que la barre soit donnée en premier. Et foo, est défini plus tard car ses chances (plutôt utiliser la probabilité) sont moindres, non?Décompilons pour voir ce que GCC 4.8 en fait
Blagovest a mentionné l'inversion de branche pour améliorer le pipeline, mais les compilateurs actuels le font-ils vraiment? Découvrons-le!
Sans pour autant
__builtin_expect
Compilez et décompilez avec GCC 4.8.2 x86_64 Linux:
Production:
L'ordre des instructions en mémoire est resté inchangé: d'abord
puts
, puisretq
retour.Avec
__builtin_expect
Remplacez maintenant
if (i)
par:et nous obtenons:
Le a
puts
été déplacé à la toute fin de la fonction, leretq
retour!Le nouveau code est fondamentalement le même que:
Cette optimisation n'a pas été faite avec
-O0
.Mais bonne chance pour écrire un exemple qui fonctionne plus vite avec
__builtin_expect
que sans, les processeurs sont vraiment intelligents ces jours-ci . Mes tentatives naïves sont là .C ++ 20
[[likely]]
et[[unlikely]]
C ++ 20 a normalisé ces composants intégrés C ++: Comment utiliser l'attribut probable / improbable de C ++ 20 dans l'instruction if-else Ils feront probablement (un jeu de mots!) La même chose.
la source
L'idée de
__builtin_expect
est de dire au compilateur que vous trouverez généralement que l'expression est évaluée à c, afin que le compilateur puisse optimiser pour ce cas.Je suppose que quelqu'un a pensé qu'il était intelligent et qu'il accélérait les choses en faisant cela.
Malheureusement, à moins que la situation ne soit très bien comprise (il est probable qu'ils n'aient rien fait de tel), cela aurait pu aggraver les choses. La documentation dit même:
En général, vous ne devriez pas utiliser à
__builtin_expect
moins que:la source
__builtin_expect
ou non . D'un autre côté, le compilateur peut effectuer de nombreuses optimisations en fonction de la probabilité de branchement, comme organiser le code de sorte que le chemin actif soit contigu, déplacer le code peu susceptible d'être optimisé plus loin ou réduire sa taille, prendre des décisions sur les branches à vectoriser, mieux planifier le chemin chaud, et ainsi de suite.Eh bien, comme il est dit dans la description, la première version ajoute un élément prédictif à la construction, indiquant au compilateur que la
x == 0
branche est la plus probable - c'est-à-dire que c'est la branche qui sera prise le plus souvent par votre programme.Dans cet esprit, le compilateur peut optimiser le conditionnel afin qu'il nécessite le moins de travail lorsque la condition attendue est satisfaite, au détriment peut-être d'avoir à faire plus de travail en cas de condition inattendue.
Regardez comment les conditions sont implémentées pendant la phase de compilation, ainsi que dans l'assembly résultant, pour voir comment une branche peut être moins efficace que l'autre.
Cependant, je n'attendre cette optimisation ait un effet notable si la condition en question fait partie d'une boucle intérieure étanche qui est appelé un grand nombre , puisque la différence dans le code qui en résulte est relativement faible. Et si vous l'optimisez dans le mauvais sens, vous risquez de diminuer vos performances.
la source
compiler design - Aho, Ullmann, Sethi
:-)Je ne vois aucune des réponses à la question que je pense que vous posiez, paraphrasée:
Le titre de votre question m'a fait penser à le faire de cette façon:
Si le compilateur suppose que «true» est plus probable, il pourrait être optimisé pour ne pas appeler
foo()
.Le problème ici est simplement que vous ne savez pas, en général, ce que le compilateur supposera - donc tout code qui utilise ce type de technique devrait être soigneusement mesuré (et éventuellement surveillé au fil du temps si le contexte change).
la source
else
été omise du corps du message.Je le teste sur Mac selon @Blagovest Buyukliev et @Ciro. Les assemblages semblent clairs et j'ajoute des commentaires;
Les commandes sont
gcc -c -O3 -std=gnu11 testOpt.c; otool -tVI testOpt.o
Quand j'utilise -O3 , ça a la même apparence, peu importe que __builtin_expect (i, 0) existe ou non.
Lors de la compilation avec -O2 ,, il semble différent avec et sans __builtin_expect (i, 0)
D'abord sans
Maintenant avec __builtin_expect (i, 0)
Pour résumer, __builtin_expect fonctionne dans le dernier cas.
la source