编译器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并调用函数,不省略循环。
此外,必须打开编译器优化选项才能尝试如上所述的优化。