我們可以使用編譯器Explorer訪問godbolt.org
看你的例子。我們將使用下面的測試平臺代碼:
int test() {
int a[15] = {0};
int b[15] = {0};
for (int i = 0; i < 15; i++){
a[b[i]]++;
}
return 0;
}
Godbolt顯示x86彙編,不是LLVM字節碼,但我總結了一點,以顯示這是怎麼回事。這是在-O0 -m32
:
test():
# set up stack
.LBB0_1:
cmp dword ptr [ebp - 128], 15 # i < 15?
jge .LBB0_4 # no? then jump out of loop
mov eax, dword ptr [ebp - 128] # load i
mov eax, dword ptr [ebp + 4*eax - 124] # load b[i]
mov ecx, dword ptr [ebp + 4*eax - 64] # load a[b[i]]
add ecx, 1 # increment it
mov dword ptr [ebp + 4*eax - 64], ecx # store it back
mov eax, dword ptr [ebp - 128]
add eax, 1 # increment i
mov dword ptr [ebp - 128], eax
jmp .LBB0_1 # repeat
.LBB0_4:
# tear down stack
ret
這看起來像我們預期:環清晰可見,它所有我們列出的步驟。如果我們編譯在,我們看到了循環仍然存在,但它的簡單得多:
test(): # @test()
# set up stack
.LBB0_1:
mov ecx, dword ptr [esp + 4*eax] # load b[i]
inc dword ptr [esp + 4*ecx + 60] # increment a[b[i]]
inc eax # increment i
cmp eax, 15 # compare == 15
jne .LBB0_1 # no? then loop
# tear down stack
ret
鏘現在使用inc
指令(有用的),發現它可以使用eax
寄存器循環計數器i
(純) ,並將條件檢查移到循環的底部(可能更好)。不過,我們仍然可以識別我們的原始代碼。現在讓我們試試-O2 -m32 -march=i386
:
test():
xor eax, eax # does nothing
ret
就是這樣嗎?是。
clang
檢測到a
數組永遠不能在函數之外使用。這意味着執行增量操作不會影響程序的其他任何部分 - 也不會在程序失效時錯過它。
刪除增量會留下一個for
循環,其中包含一個空體並且沒有副作用,也可以將其刪除。反過來,去除循環留下了(爲了所有意圖和目的)空函數。
這個空函數可能是你在LLVM字節碼中看到的(ret i32 0
)。
這並不是一個非常科學的描述,clang
花費可能會有所不同的步驟,但我希望示例清除它一點。如果你願意,你可以在as-if rule上閱讀。例如,我還建議在https://godbolt.org/附近玩一下:看看將a
和b
移到該函數外面會發生什麼。
如果您想逐步查看優化,請嘗試:'clang -mllvm -print-after-all' – Joky