簡體   English   中英

gcc C if語句優化

[英]gcc C if statement optimization

gcc是否優化代碼,例如:

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

如果cond是常數? 此外,什么是學習C優化的良好資源? (因此,我不必再問像這樣的愚蠢問題)

是的,如果編譯器證明允許這樣做並且認為有價值,那么肯定會對其進行優化。

我嘗試了最新版本的gcciccclang ,它們都將上述代碼有效地進行了優化:

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

也就是說,他們復制了對func2()的調用以啟用此優化。 這是來自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

當然,不能保證這樣的優化-編譯器可能會認為這樣做不值得,並且可能取決於特定於編譯器的試探法和編譯選項。 例如,當使用-Os (與我上面使用的-O2相反), gcc不再重新排列它,而是按照您編寫時或多或少地使用兩條cmp指令對其進行編譯:

        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

另一方面, clangicc在復制call同時繼續通過單個比較對其進行編譯。

您可以在Godbolt上玩所有這些。

通常是的。 公共子表達式在函數作用域中計算,因此如果if(x * x / y> z + z)出現兩次,x,y和z不變,則該表達式將僅計算一次。 但是,編譯器需要知道,例如,變量的地址尚未被獲取並傳遞給某個地方的子例程。

是。

您可以通過查看匯編代碼來檢查編譯器在做什么。 我已經創建了一個可以使用的C程序,以確保它具有一定的可變性,因此優化器不僅可以不斷折疊所有內容。

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

您可以使用-S生成程序集。

gcc -S test.c -o test.asm

請注意,這是沒有優化的。 您可以找到比較而不必閱讀太多匯編程序,而是查找89,它是Y的ASCII十進制表示形式。

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

那是兩個if塊的相當死板的翻譯。 您會看到兩個cmpb $89, -1(%rbp)調用,指示兩次比較。

現在進行了優化: 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

現在只有一個cmpb $89, %al ,但是也有多個movl $89, %esi lC0,lC1和lC2是與這三個函數相對應的字符串"func1: %c\\n""func2: %c\\n""func3: %c\\n"

為了進行比較,這是clang的功能。

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

與gcc一樣,現在只有一個比較。 並且,類似地,L_.str,L_.str.1和L.str.2分別是func1,func2和func3中的字符串。

實質上,兩者都將代碼更改為此。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM