編譯器For語句最佳化
編譯器在什麼條件下最佳化For語句?
該部落格上撰寫的文章是出於教學目的而創建的。 另外,文章中的資訊可能不正確,請謹慎。
從CPU的角度來看,For語句中使用的JMP指令是: 因為當管道損壞或分支預測失敗時會出現效能下降, 如果符合條件,編譯器會嘗試不使用 JMP 指令。
- 當編譯時確認迭代次數為常數時。
可最佳化:for (int i = 0; i < 4; i++) (知道會重複4次) 最佳化難度:for(int i = 0; i < n; i++)(如果n是執行時期確定的變數)
//原始碼
for (int i = 0; i < 3; i++) {
arr[i] = 0;
}
//編譯器最佳化後
arr[0] = 0;
arr[1] = 0;
arr[2] = 0;
優化前的組合語言
.L2:
cmp DWORD PTR [rbp-4], 2 ;比較如果 i < 3
jg .L3 ;如果不滿足條件,則循環結束(跳到L3)。
mov eax, DWORD PTR [rbp-4]
cdqe
mov DWORD PTR [rbp-32+rax*4], 0 ;arr[i] = 0 賦值
add DWORD PTR [rbp-4], 1 ; i++
jmp .L2 ;無條件跳回循環起點 (.L2) (JMP)
.L3:
優化的彙編語言
mov DWORD PTR [rdi], 0 ; arr[0] = 0
mov DWORD PTR [rdi+4], 0 ;arr[1] = 0(+4,因為 int 是 4 個位元組)
mov DWORD PTR [rdi+8], 0 ;arr[2] = 0(+8,因為 int 是 4 個位元組)
- 最終結果何時可以在編譯時提前計算。
當循環內部的邏輯不依賴外部輸入和輸出時,結果值將被替換為常數。
//原始碼
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
//編譯器最佳化後
//JMP 在運作時甚至不執行加法運算。
return 5050;
優化前的組合語言
.L5:
cmp DWORD PTR [rbp-4], 100 ;比較如果 i <= 100
jg .L6 ;或者結束循環
mov eax, DWORD PTR [rbp-4]
add DWORD PTR [rbp-8], eax ; sum += i
add DWORD PTR [rbp-4], 1 ; i++
jmp .L5 ;跳回循環開始 (.L5) (JMP)
.L6:
mov eax, DWORD PTR [rbp-8] ;準備返回結果
優化後的彙編語言
mov eax, 5050 ;將計算結果值5050插入eax暫存器中。
ret ;立即返回
- 即使進行循環擴展,程式碼長度也很短。
像程式碼 1 那樣展開循環稱為循環展開或循環展開。 如果因為循環擴充而使程式碼長度變得過長,就浪費了CPU的指令高速緩衝記憶體。 如果循環內的程式碼大小不小,則循環展開不會被最佳化。
- 當循環內部沒有複雜的控制流程時。
迴圈內有break、continue、goto或複雜的if-else語句,或者 如果包含外部函數調用,編譯器不會不小心擴展循環。
如果有printf等I/O函數, 由於我們必須保護螢幕上顯示的副作用,所以我們老實地運行JMP並調用函數,不省略循環。
此外,必須開啟編譯器最佳化選項才能嘗試如上所述的最佳化。