2013-02-09 11 views
11

verwenden? Die GCC manual zeigt nur Beispiele, in denen __builtin_expect() um die gesamte Bedingung einer 'if' Anweisung herum angeordnet ist.Kann ich __builtin_expect() von GCC mit ternärem Operator in C

Ich habe auch bemerkt, dass GCC nicht beschweren, wenn ich es zum Beispiel mit einem ternären Operator oder in einem beliebigen beliebigen ganzzahligen Ausdruck für diese Angelegenheit, auch eine, die nicht in einem Verzweigungskontext verwendet wird.

Also frage ich mich, was die zugrunde liegenden Einschränkungen seiner Verwendung tatsächlich sind.

Wird es seine Wirkung beibehalten, wenn sie in einem ternären Operation wie folgt verwendet:

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

Und was ist mit diesem Fall:

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

Und dieses:

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

Antwort

8

Es funktioniert offenbar sowohl für ternäre als auch für regelmäßige if-Anweisungen.

Zunächst sehen wir uns die folgenden drei Codebeispiele an, von denen zwei __builtin_expect sowohl im regulären als auch im ternären if-Stil verwenden, und ein dritter, der es überhaupt nicht verwendet.

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); 
} 

Wenn mit -O3 zusammengestellt, die alle drei Ergebnisse in der gleichen Anordnung. Wenn jedoch die -O weggelassen wird (auf GCC 4.7.2), die beide ternary.c und builtin.c haben die gleiche Assembler-Liste (wo es wichtig ist):

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 

Während nobuiltin.c nicht der Fall ist:

.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 

Der relevante Teil:

diff

Grundsätzlich __builtin_expect Ursachen zusätzlicher Code (sete %al ...) vor den auf dem Ergebnis der testl %eax, %eax basierend je .L2 ausgeführt werden, die die CPU wahrscheinlicher ist als eine vorherzusagen (naive Annahme, hier) anstatt basierend auf dem direkten Vergleich der Eingabe char mit 'c'. Während im nobuiltin.c-Fall kein solcher Code existiert und der je/jne direkt dem Vergleich mit 'c' folgt (cmp $99).Denken Sie daran, Verzweigungsvorhersage wird hauptsächlich in der CPU durchgeführt, und hier legt GCC einfach eine Falle für den CPU-Verzweigungsvorhersager, um anzunehmen, welcher Pfad genommen wird (über den zusätzlichen Code und die Umschaltung von je und jne, obwohl ich nicht tue habe eine Quelle dafür, da Intels official optimization manual nicht erwähnt, erste Begegnungen mit je vs jne anders für die Verzweigungsprognose zu behandeln! Ich kann nur annehmen, dass das GCC-Team über Versuch und Irrtum hier angekommen ist).

Ich bin mir sicher, dass es bessere Testfälle gibt, in denen GCCs Verzweigungsvorhersage direkter gesehen werden kann (anstatt Hinweise auf die CPU zu beobachten), obwohl ich nicht weiß, wie man einen solchen Fall prägnant nachbildet. (Vermutung: Es würde wahrscheinlich Schleife Abrollung während der Kompilierung beinhalten.)

+0

Sehr schöne Analyse und sehr schöne Präsentation der Ergebnisse. Danke für die Mühe. –

+1

Dies zeigt nicht wirklich etwas anderes als dass "__builtin_expect" keinen Einfluss auf den optimierten Code für x86 hat (da Sie sagten, dass sie mit -O3 identisch waren). Der einzige Grund, warum sie vorher anders sind, ist, dass "__builtin_expect" eine Funktion ist, die den ihr gegebenen Wert zurückgibt, und dass der Rückgabewert nicht durch Flags passieren kann. Andernfalls würde der Unterschied im optimierten Code bleiben. – ughoavgfhw

+0

@ughoavgfhw: Was meinst du mit "dieser Rückgabewert kann nicht durch Flaggen passieren"? –