2017-01-30 4 views
1

Does gcc optimize Code wie:gcc C if-Anweisung Optimierung

if(cond) 
    func1() 
func2() 
if(cond) 
    func3() 

Wenn cond konstant ist? Und was sind gute Ressourcen, um mehr über C-Optimierungen zu erfahren? (Also muss ich keine dummen Fragen wie diese stellen)

+0

Mit "invariant" meinen Sie "konstant"? –

+1

Hängt davon ab. Ändert 'func2()' 'cond'? Kann 'gcc' * beweisen * dass es nicht ist? – EOF

+0

Überprüfen Sie, indem Sie auf den Baugruppenausgang schauen. (Ich bin 100% sicher, dass es das optimiert.) Ich nehme an, mit "invariant" meinen Sie "konstanter Ausdruck". – Downvoter

Antwort

4

Ja Compiler werden dies sicherlich optimieren, wenn sie beweisen, dass es erlaubt ist und wenn es sich lohnt.

Ich habe versucht, auf aktuellere Version von gcc, icc und clang und sie alle optimiert den obigen Code zu, effektiv:

if (cond) { 
    func1(); 
    func2(); 
    func3(); 
} else { 
    func2(); 
} 

Das heißt, sie den Anruf zu func2() dupliziert diese Optimierung zu ermöglichen. Hier ist ein typisches Beispiel für den Code kompiliert, von gcc:

 cmp  edi, 33 
     je  .L7 
     xor  eax, eax 
     jmp  func2 
.L7: 
     sub  rsp, 8 
     xor  eax, eax 
     call func1 
     xor  eax, eax 
     call func2 
     xor  eax, eax 
     add  rsp, 8 
     jmp  func3 

Natürlich ist eine solche Optimierung nicht garantiert ist - ein Compiler entscheiden, kann es nicht wert ist, und es kann auf Compiler- abhängig spezifische Heuristiken und Kompilierungsoptionen. Zum Beispiel bei der Verwendung von -Os (im Gegensatz zu -O2 gegenüber, die ich oben verwendet), gcc nicht mehr neu ordnet sie und stattdessen kompiliert es mehr oder weniger, wie Sie es geschrieben haben, mit zwei cmp Anweisungen:

 cmp  edi, 33 
     jne  .L5 
     xor  eax, eax 
     call func1 
.L5: 
     xor  eax, eax 
     call func2 
     cmp  edi, 33 
     jne  .L4 
     xor  eax, eax 
     jmp  func3 
.L4: 
     ret 

Auf der anderen Seite, beide clang und icc weiterhin kompilieren es mit einem einzigen Vergleich beim Duplizieren der call.

Sie können mit all dem auf godbolt spielen.

+0

Beeindruckend! Vielen Dank –

1

Im Allgemeinen ja. Gemeinsame Unterausdrücke werden im Funktionsumfang berechnet. Wenn also if (x * x/y> z + z) zweimal erscheint, x, y und z unverändert, wird der Ausdruck nur einmal berechnet. Allerdings muss der Compiler beispielsweise wissen, dass die Adresse der Variablen nicht übernommen wurde und irgendwo an eine Unterroutine übergeben wurde.

1

Ja.

Sie können überprüfen, was ein Compiler tut, indem Sie seinen Assemblycode betrachten. Ich habe ein kleines C-Programm erstellt, um sicherzustellen, dass es etwas Variabilität hat, so dass der Optimierer nicht einfach alles konstant faltet.

#include <stdio.h> 

void func1(const char input) { 
    printf("func1: %c\n", input); 
} 

void func2(const char input) { 
    printf("func2: %c\n", input); 
} 

void func3(const char input) { 
    printf("func3: %c\n", input); 
} 

int main() { 
    char input = (char)getchar(); 

    if(input == 'Y') { 
     func1(input); 
    } 

    func2(input); 

    if(input == 'Y') { 
     func3(input); 
    } 
} 

Sie können die Baugruppe mit -S generieren.

gcc -S test.c -o test.asm 

Beachten Sie, dass dies ohne Optimierungen ist. Sie können den Vergleich finden, ohne viel Assembly lesen zu müssen, suchen Sie nach 89, die die ASCII-Dezimal-Darstellung von Y ist.

LCFI10: 
     subq $16, %rsp 
     call _getchar 
     movb %al, -1(%rbp) 
     cmpb $89, -1(%rbp) 
     jne  L5 
     movsbl -1(%rbp), %eax 
     movl %eax, %edi 
     call _func1 
L5: 
     movsbl -1(%rbp), %eax 
     movl %eax, %edi 
     call _func2 
     cmpb $89, -1(%rbp) 
     jne  L6 
     movsbl -1(%rbp), %eax 
     movl %eax, %edi 
     call _func3 
L6: 
     movl $0, %eax 
     leave 

Das ist eine ziemlich rote Übersetzung der beiden if-Blöcke. Sie können zwei cmpb $89, -1(%rbp) Aufrufe sehen, die anzeigen, dass der Vergleich zweimal durchgeführt wird.

Jetzt mit Optimierungen: gcc -S -O3 test.c -o test.asm

LCFI0: 
     call _getchar 
     cmpb $89, %al 
     je  L10 
     leaq lC1(%rip), %rdi 
     movsbl %al, %esi 
     xorl %eax, %eax 
     call _printf 
L7: 
     xorl %eax, %eax 
     addq $8, %rsp 
LCFI1: 
     ret 
L10: 
LCFI2: 
     leaq lC0(%rip), %rdi 
     movl $89, %esi 
     xorl %eax, %eax 
     call _printf 
     movl $89, %esi 
     xorl %eax, %eax 
     leaq lC1(%rip), %rdi 
     call _printf 
     movl $89, %esi 
     xorl %eax, %eax 
     leaq lC2(%rip), %rdi 
     call _printf 
     jmp  L7 

Jetzt ist es nur ein cmpb $89, %al, aber es gibt auch mehr movl $89, %esi. IC0, IC1 und IC2 sind die Strings "func1: %c\n", "func2: %c\n" und "func3: %c\n", die den drei Funktionen entsprechen.

Zum Vergleich, hier ist was Clang tut.

Ltmp12: 
     .cfi_offset %rbx, -24 
     callq _getchar 
     movsbl %al, %ebx 
     movzbl %bl, %eax 
     cmpl $89, %eax 
     jne  LBB3_2 
## BB#1: 
     leaq L_.str(%rip), %rdi 
     xorl %eax, %eax 
     movl %ebx, %esi 
     callq _printf 
     leaq L_.str.1(%rip), %rdi 
     xorl %eax, %eax 
     movl %ebx, %esi 
     callq _printf 
     leaq L_.str.2(%rip), %rdi 
     jmp  LBB3_3 
LBB3_2: 
     leaq L_.str.1(%rip), %rdi 

Wie bei gcc gibt es jetzt nur einen Vergleich. Und in ähnlicher Weise sind L_.str, L_.str.1 und L.str.2 die Zeichenfolgen in func1, func2 bzw. func3.

Beide haben im Wesentlichen den Code geändert, um dies zu sein.

if(input == 'Y') { 
    printf("func1: %c\n", input); 
    printf("func2: %c\n", input); 
    printf("func3: %c\n", input); 
} 
else { 
    printf("func2: %c\n", input); 
}