2013-02-09 7 views
11

Lo GCC manual mostra solo esempi in cui __builtin_expect() è posizionato attorno all'intera condizione di un'istruzione "if".È possibile utilizzare __builtin_expect() di GCC con l'operatore ternario in C

Ho anche notato che GCC non si lamenta se lo uso, ad esempio, con un operatore ternario, o in qualsiasi espressione integrale arbitraria per quella materia, anche una che non viene utilizzata in un contesto di ramificazione.

Quindi, mi chiedo quali siano i reali vincoli del suo utilizzo.

Sarà mantenere il suo effetto quando viene utilizzato in un'operazione ternario come questo:

int foo(int i) 
{ 
    return __builtin_expect(i == 7, 1) ? 100 : 200; 
} 

E che dire di questo caso:

int foo(int i) 
{ 
    return __builtin_expect(i, 7) == 7 ? 100 : 200; 
} 

E questo:

int foo(int i) 
{ 
    int j = __builtin_expect(i, 7); 
    return j == 7 ? 100 : 200; 
} 

risposta

8

E ' apparentemente funziona sia per le dichiarazioni ternarie che per quelle regolari.

In primo luogo, diamo un'occhiata ai seguenti tre esempi di codice, due dei quali usano __builtin_expect in entrambi gli stili regular-if e ternary-if e un terzo che non lo usa affatto.

builtin.c:

int main() 
{ 
    char c = getchar(); 
    const char *printVal; 
    if (__builtin_expect(c == 'c', 1)) 
    { 
     printVal = "Took expected branch!\n"; 
    } 
    else 
    { 
     printVal = "Boo!\n"; 
    } 

    printf(printVal); 
} 

ternary.c:

int main() 
{ 
    char c = getchar(); 
    const char *printVal = __builtin_expect(c == 'c', 1) 
     ? "Took expected branch!\n" 
     : "Boo!\n"; 

    printf(printVal); 
} 

nobuiltin.c:

int main() 
{ 
    char c = getchar(); 
    const char *printVal; 
    if (c == 'c') 
    { 
     printVal = "Took expected branch!\n"; 
    } 
    else 
    { 
     printVal = "Boo!\n"; 
    } 

    printf(printVal); 
} 

Quando viene compilato con -O3, tutti e tre risultato nello stesso assieme. Tuttavia, quando il -O è lasciato fuori (su GCC 4.7.2), sia ternary.c e builtin.c hanno la stessa lista di montaggio (dove conta):

builtin.s:

.file "builtin.c" 
    .section .rodata 
.LC0: 
    .string "Took expected branch!\n" 
.LC1: 
    .string "Boo!\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    movl %esp, %ebp 
    .cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $32, %esp 
    call getchar 
    movb %al, 27(%esp) 
    cmpb $99, 27(%esp) 
    sete %al 
    movzbl %al, %eax 
    testl %eax, %eax 
    je .L2 
    movl $.LC0, 28(%esp) 
    jmp .L3 
.L2: 
    movl $.LC1, 28(%esp) 
.L3: 
    movl 28(%esp), %eax 
    movl %eax, (%esp) 
    call printf 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Debian 4.7.2-4) 4.7.2" 
    .section .note.GNU-stack,"",@progbits 

ternary.s:

.file "ternary.c" 
    .section .rodata 
.LC0: 
    .string "Took expected branch!\n" 
.LC1: 
    .string "Boo!\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    movl %esp, %ebp 
    .cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $32, %esp 
    call getchar 
    movb %al, 31(%esp) 
    cmpb $99, 31(%esp) 
    sete %al 
    movzbl %al, %eax 
    testl %eax, %eax 
    je .L2 
    movl $.LC0, %eax 
    jmp .L3 
.L2: 
    movl $.LC1, %eax 
.L3: 
    movl %eax, 24(%esp) 
    movl 24(%esp), %eax 
    movl %eax, (%esp) 
    call printf 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Debian 4.7.2-4) 4.7.2" 
    .section .note.GNU-stack,"",@progbits 

Mentre nobuiltin.c non:

.file "nobuiltin.c" 
    .section .rodata 
.LC0: 
    .string "Took expected branch!\n" 
.LC1: 
    .string "Boo!\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    movl %esp, %ebp 
    .cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $32, %esp 
    call getchar 
    movb %al, 27(%esp) 
    cmpb $99, 27(%esp) 
    jne .L2 
    movl $.LC0, 28(%esp) 
    jmp .L3 
.L2: 
    movl $.LC1, 28(%esp) 
.L3: 
    movl 28(%esp), %eax 
    movl %eax, (%esp) 
    call printf 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Debian 4.7.2-4) 4.7.2" 
    .section .note.GNU-stack,"",@progbits 

La parte rilevante:

diff

In sostanza, __builtin_expect cause codice aggiuntivo (sete %al ...) da eseguire prima della je .L2 sulla base dei risultati di testl %eax, %eax che è più probabile di predire come 1 (ipotesi ingenua, qui) la CPU invece di basarsi sul confronto diretto del char di input con 'c'. Considerando che nel caso nobuiltin.c, non esiste un tale codice e lo je/jne segue direttamente il confronto con 'c' (cmp $99).Ricorda che la previsione delle diramazioni avviene principalmente nella CPU, e qui GCC sta semplicemente "ponendo una trappola" affinché il predittore del ramo CPU assuma quale percorso verrà preso (tramite il codice aggiuntivo e la commutazione di je e jne, sebbene io non avere una fonte per questo, come Intel official optimization manual non menziona il trattamento di primi incontri con vs jne in modo diverso per la previsione di ramo! Posso solo supporre che il team GCC è arrivato a questo via tentativi ed errori).

Sono sicuro che ci sono casi di test migliori in cui la previsione delle branch di GCC può essere vista più direttamente (invece di osservare i suggerimenti per la CPU), anche se non so come emulare un caso in modo sintetico/conciso. (Indovina: probabilmente comporterà lo srotolamento del loop durante la compilazione.)

+0

Analisi molto bella e presentazione molto bella dei risultati. Grazie per lo sforzo. –

+1

Questo non mostra nulla di diverso dal fatto che '__builtin_expect' non ha effetto sul codice ottimizzato per x86 (dato che hai detto che erano uguali a -O3). L'unica ragione per cui sono diversi prima è che '__builtin_expect' è una funzione che restituisce il valore dato ad esso, e che il valore di ritorno non può avvenire attraverso i flag. Altrimenti, la differenza rimarrebbe nel codice ottimizzato. – ughoavgfhw

+0

@ughoavgfhw: Cosa intendi per "che il valore restituito non può avvenire tramite le bandiere"? –