190 votes

Quel est l'avantage de la fonction __builtin_expect de GCC dans les instructions if else ?

Je suis tombé sur un #define dans laquelle 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 la branche de prédiction de branche. En général, il est préférable d'utiliser le pour cela ( -fprofile-arcs ), car les programmeurs sont programmeurs sont notoirement mauvais pour prédire les performances réelles de leurs programmes. Cependant, il existe des applications pour lesquelles ces données sont difficiles à collecter.

La valeur de retour est la valeur de exp qui devrait être une expression intégrale. La sémantique de l'intégré est que l'on s'attend à ce que exp == c . Par exemple :

      if (__builtin_expect (x, 0))
        foo ();

indiquerait que nous ne nous attendons pas à appeler foo puisque nous attendons x à zéro.

Alors pourquoi ne pas l'utiliser directement :

if (x)
    foo ();

au lieu de la syntaxe compliquée avec __builtin_expect ?

5 votes

Je pense que votre direct Le code aurait dû être if ( x == 0) {} else foo(); ou simplement if ( x != 0 ) foo(); qui est équivalent au code de la documentation GCC.

0voto

user3762471 Points 1

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

Lorsque j'utilise -O3 ,, l'apparence est la même, que __builtin_expect(i, 0) existe ou non.

testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp     
0000000000000001    movq    %rsp, %rbp    // open function stack
0000000000000004    xorl    %edi, %edi       // set time args 0 (NULL)
0000000000000006    callq   _time      // call time(NULL)
000000000000000b    testq   %rax, %rax   // check time(NULL)  result
000000000000000e    je  0x14           //  jump 0x14 if testq result = 0, namely jump to puts
0000000000000010    xorl    %eax, %eax   //  return 0   ,  return appear first 
0000000000000012    popq    %rbp    //  return 0
0000000000000013    retq                     //  return 0
0000000000000014    leaq    0x9(%rip), %rdi  ## literal pool for: "a"  // puts  part, afterwards
000000000000001b    callq   _puts
0000000000000020    xorl    %eax, %eax
0000000000000022    popq    %rbp
0000000000000023    retq

Lorsque l'on compile avec -O2 ,, l'apparence est différente avec et sans __builtin_expect(i, 0).

D'abord sans

testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    xorl    %edi, %edi
0000000000000006    callq   _time
000000000000000b    testq   %rax, %rax
000000000000000e    jne 0x1c       //   jump to 0x1c if not zero, then return
0000000000000010    leaq    0x9(%rip), %rdi ## literal pool for: "a"   //   put part appear first ,  following   jne 0x1c
0000000000000017    callq   _puts
000000000000001c    xorl    %eax, %eax     // return part appear  afterwards
000000000000001e    popq    %rbp
000000000000001f    retq

Maintenant avec __builtin_expect(i, 0)

testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    xorl    %edi, %edi
0000000000000006    callq   _time
000000000000000b    testq   %rax, %rax
000000000000000e    je  0x14   // jump to 0x14 if zero  then put. otherwise return 
0000000000000010    xorl    %eax, %eax   // return appear first 
0000000000000012    popq    %rbp
0000000000000013    retq
0000000000000014    leaq    0x7(%rip), %rdi ## literal pool for: "a"
000000000000001b    callq   _puts
0000000000000020    jmp 0x10

Pour résumer, __builtin_expect fonctionne dans le dernier cas.

0voto

Alexis Paques Points 1022

Dans la plupart des cas, vous devez laisser la prédiction de la branche telle qu'elle est et vous n'avez pas à vous en préoccuper.

Un cas où elle est bénéfique est celui des algorithmes intensifs pour le CPU avec beaucoup de branchements. Dans certains cas, les sauts peuvent conduire à dépasser le cache actuel du programme du CPU, ce qui oblige ce dernier à attendre l'exécution de la partie suivante du logiciel. En poussant les branches improbables à la fin, vous garderez votre mémoire proche et ne sauterez que pour les cas improbables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X