2016-08-18 14 views
6

Jeśli mam:Korzystanie Likely()/Nieprawdopodobne() Preprocessor Makra w if-else if łańcuch

#define likely(x)  __builtin_expect((x),1) 
#define unlikely(x)  __builtin_expect((x),0) 

if (A) 
    return true; 
else if (B) 
    return false; 
... 
else if (Z) 
    return true; 
else 
    //this will never really happen!!!! 
    raiseError(); 
    return false; 

mogę umieścić prawdopodobne() po ostatniej stan sprawdzania jak else if (likely(Z)) oznaczać, że końcowa deklaracja (inaczej) jest bardzo mało prawdopodobne, BEZ kompilatora wpływającego na przewidywanie rozgałęzień poprzednich kontroli?

Zasadniczo, czy GCC próbuje zoptymalizować cały blok if-else if, jeśli istnieje pojedyncza instrukcja warunkowa z podpowiedź predyktora gałęzi?

+0

Jedynym sposobem, aby się przekonać, jest zbudowanie (oczywiście z włączonymi optymalizacjami) i sprawdzenie wygenerowanego kodu. Porównaj kod wygenerowany zi bez 'prawdopodobnego' użycia. –

+0

Dlaczego nie postawić "mało prawdopodobnego" w innych warunkach? –

+0

@KerrekSB Właśnie tego chcę zapobiec. Wszystkie warunki są równie prawdopodobne, z wyjątkiem warunku, że żadna z nich nie jest prawdziwa. – Leo

Odpowiedz

8

Uczynisz to wyraźne:

if (A) 
    return true; 
else if (B) 
    return true; 
... 
else if (Y) 
    return true; 
else { 
    if (likely(Z)) 
    return true; 

    raiseError(); 
    return false; 
} 

Teraz kompilator jasno rozumie swoją intencję i nie będzie przypisać inne prawdopodobieństw oddziałów. Zwiększono również czytelność kodu.

P.S. Proponuję, aby przepisać również prawdopodobne i nieprawdopodobne w sposób jądro Linux zrobić, aby chronić od cichych integralnych odlewów:

#define likely(x)  __builtin_expect(!!(x), 1) 
#define unlikely(x) __builtin_expect(!!(x), 0) 
2

Ogólnie, GCC zakłada, że ​​warunkowe w instrukcji if będzie prawdziwe - istnieją wyjątki, ale są kontekstowy.

extern int s(int); 

int f(int i) { 
    if (i == 0) 
    return 1; 
    return s(i); 
} 

produkuje

f(int): 
     testl %edi, %edi 
     jne  .L4 
     movl $1, %eax 
     ret 
.L4: 
     jmp  s(int) 

podczas

extern int t(int*); 
int g(int* ip) { 
    if (!ip) 
    return 0; 
    return t(ip); 
} 

produkuje:

g(int*): 
     testq %rdi, %rdi 
     je  .L6 
     jmp  t(int*) 
.L6: 
     xorl %eax, %eax 
     ret 

(patrz godbolt)

Zobacz, jak w f gałąź jest jne (zakładamy, że warunek jest prawdziwy), natomiast w g zakłada się, że warunek jest fałszywy.

teraz porównać z następujących czynności:

extern int s(int); 
extern int t(int*); 

int x(int i, int* ip) { 
    if (!ip) 
    return 1; 
    if (!i) 
    return 2; 
    if (s(i)) 
    return 3; 
    if (t(ip)) 
    return 4; 
    return s(t(ip)); 
} 

która produkuje

x(int, int*): 
     testq %rsi, %rsi 
     je  .L3   # first branch: assumed unlikely 
     movl $2, %eax 
     testl %edi, %edi 
     jne  .L12  # second branch: assumed likely 
     ret 
.L12: 
     pushq %rbx 
     movq %rsi, %rbx 
     call s(int) 
     movl %eax, %edx 
     movl $3, %eax 
     testl %edx, %edx 
     je  .L13  # third branch: assumed likely 
.L2: 
     popq %rbx 
     ret 
.L3: 
     movl $1, %eax 
     ret 
.L13: 
     movq %rbx, %rdi 
     call t(int*) 
     movl %eax, %edx 
     movl $4, %eax 
     testl %edx, %edx 
     jne  .L2  # fourth branch: assumed unlikely! 
     movq %rbx, %rdi 
     call t(int*) 
     popq %rbx 
     movl %eax, %edi 
     jmp  s(int) 

Tutaj widzimy jeden z czynników kontekstowych: GCC dostrzeżone, że może to ponownego wykorzystania L2 tutaj, więc postanowił uznają ostateczne warunkowe za mało prawdopodobne, aby mogło emitować mniej kodu.

Spójrzmy na montażu przykład dałeś:

#define likely(x)  __builtin_expect((x),1) 
#define unlikely(x)  __builtin_expect((x),0) 

extern void raiseError(); 

int f(int A, int B, int Z) 
{ 
    if (A) 
    return 1; 
    else if (B) 
    return 2; 
    else if (Z) 
    return 3; 

    raiseError(); 
    return -1; 
} 

Zespół looks like this:

f(int, int, int): 
     movl $1, %eax 
     testl %edi, %edi 
     jne  .L9 
     movl $2, %eax 
     testl %esi, %esi 
     je  .L11 
.L9: 
     ret 
.L11: 
     testl %edx, %edx 
     je  .L12  # branch if !Z 
     movl $3, %eax 
     ret 
.L12: 
     subq $8, %rsp 
     call raiseError() 
     movl $-1, %eax 
     addq $8, %rsp 
     ret 

pamiętać, że generowane gałęzie kodu gdy Z jest prawdziwe, to już zachowuje się tak, jakby Z jest prawdopodobne. Co się stanie, jeśli powiemy, że Z jest prawdopodobne?

#define likely(x)  __builtin_expect((x),1) 
#define unlikely(x)  __builtin_expect((x),0) 

extern void raiseError(); 

int f(int A, int B, int Z) 
{ 
    if (A) 
    return 1; 
    else if (B) 
    return 2; 
    else if (likely(Z)) 
    return 3; 

    raiseError(); 
    return -1; 
} 

teraz we get

f(int, int, int): 
     movl $1, %eax 
     testl %edi, %edi 
     jne  .L9 
     movl $2, %eax 
     testl %esi, %esi 
     je  .L11 
.L9: 
     ret 
.L11: 
     movl $3, %eax # assume Z 
     testl %edx, %edx 
     jne  .L9   # but branch if Z 
     subq $8, %rsp 
     call raiseError() 
     movl $-1, %eax 
     addq $8, %rsp 
     ret 

Chodzi o to, że należy być ostrożnym podczas korzystania z tych makr i przed zbadać kod i po ostrożnie, aby upewnić się uzyskać wyniki, że spodziewałeś, a punktem odniesienia (np. z perf), aby upewnić się, że procesor tworzy przewidywania, które są zgodne z generowanym kodem.