[英]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 不知道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.