簡體   English   中英

未執行循環裂變/不變優化,為什么?

[英]Loop fission/invariant optimization not performed, why?

我正在嘗試了解有關匯編的更多信息以及編譯器可以做和不能做的優化。

我有一段測試代碼,對此我有一些疑問。

在此處查看實際操作: https://godbolt.org/z/pRztTT ,或查看下面的代碼和程序集。

#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[])
{
        for (int j = 0; j < 100; j++) {
                if (argc == 2 && argv[1][0] == '5') {
                        printf("yes\n");
                }
                else {
                        printf("no\n");
                }
        }

        return 0;
}

GCC 10.1 生產的組件帶有-O3:

.LC0:
        .string "no"
.LC1:
        .string "yes"
main:
        push    rbp
        mov     rbp, rsi
        push    rbx
        mov     ebx, 100
        sub     rsp, 8
        cmp     edi, 2
        je      .L2
        jmp     .L3
.L5:
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        sub     ebx, 1
        je      .L4
.L2:
        mov     rax, QWORD PTR [rbp+8]
        cmp     BYTE PTR [rax], 53
        jne     .L5
        mov     edi, OFFSET FLAT:.LC1
        call    puts
        sub     ebx, 1
        jne     .L2
.L4:
        add     rsp, 8
        xor     eax, eax
        pop     rbx
        pop     rbp
        ret
.L3:
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        sub     ebx, 1
        je      .L4
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        sub     ebx, 1
        jne     .L3
        jmp     .L4

似乎 GCC 產生了兩個版本的循環:一個有argv[1][0] == '5'條件但沒有argc == 2條件,一個沒有任何條件。

我的問題:

  • 是什么阻止 GCC 分裂出完整的條件? 它與這個問題類似,但代碼沒有機會在這里獲得指向 argv 的指針。
  • 在沒有任何條件的循環中(匯編中的L3),為什么循環體重復? 是否在仍然適合某種緩存的同時減少跳轉次數?

GCC 不知道printf不會修改由argv指向的 memory ,因此它無法解除該循環。

argc是一個局部變量(不能被任何指針全局變量指向),所以它知道調用不透明的 function 不能修改它。 證明局部變量是真正私有的是Escape Analysis的一部分。

OP 通過首先將argv[1][0]復制到本地 char 變量中對此進行了測試:這讓 GCC 將完整條件提升出循環。


實際上, argv[1]不會指向printf可以修改的 memory。 但我們只知道因為printf是一個 C 標准庫 function ,而我們假設main僅由 CRT 啟動代碼用實際命令行 arg 調用不是通過這個程序中的其他一些 function 傳遞自己的參數。 在 C(與 C++ 不同)中, main是可重入的,可以從程序內部調用。

此外,在 GNU C 中, printf可以注冊自定義格式字符串處理函數。 盡管在這種情況下,編譯器內置printf查看格式字符串並將其優化為puts調用。

所以printf已經有些特殊了,但我不認為 GCC 會費心尋找優化,因為它不會修改任何其他全局可訪問的 memory。 使用自定義 stdio output 緩沖區,這甚至可能不是真的。 printf慢; 節省一些溢出/重新加載通常沒什么大不了的。


(理論上)將 puts() 與 main() 一起編譯是否允許編譯器看到 puts() 沒有觸及 argv 並完全優化循環?

是的,例如,如果您編寫了自己的write function,它使用圍繞syscall指令的內聯 asm 語句(使用 memory 僅輸入操作數以使其安全,同時避免"memory"破壞),那么它可以內聯並假設argv[1][0]沒有被 asm 語句更改並基於它進行檢查。 即使您正在輸出argv[1]

或者可以在沒有內聯的情況下進行過程間優化。


回復:展開:這很奇怪,對於 GCC 在-O3的默認情況下-funroll-loops不啟用,只有-O3 -fprofile-use 或者如果手動啟用。

暫無
暫無

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

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