Lun*_*din 6 c assembly gcc x86-64 micro-optimization
我正在从这里查看一些递归函数:
int get_steps_to_zero(int n)
{
if (n == 0) {
// Base case: we have reached zero
return 0;
} else if (n % 2 == 0) {
// Recursive case 1: we can divide by 2
return 1 + get_steps_to_zero(n / 2);
} else {
// Recursive case 2: we can subtract by 1
return 1 + get_steps_to_zero(n - 1);
}
}
Run Code Online (Sandbox Code Playgroud)
我检查了反汇编,以检查 gcc 是否管理尾部调用优化/展开。看起来确实如此,尽管使用 x86-64 gcc 12.2 -O3 我得到了一个像这样的函数,以两条ret指令结尾:
get_steps_to_zero:
xor eax, eax
test edi, edi
jne .L5
jmp .L6
.L10:
mov edx, edi
shr edx, 31
add edi, edx
sar edi
test edi, edi
je .L9
.L5:
add eax, 1
test dil, 1
je .L10
sub edi, 1
test edi, edi
jne .L5
.L9:
ret
.L6:
ret
Run Code Online (Sandbox Code Playgroud)
多重回报的目的是什么?这是一个错误吗?
编辑
看起来这是从 gcc 11.x 开始出现的。当在 gcc 10.x 下编译时,函数结束如下:
.L1:
mov eax, r8d
ret
.L6:
xor r8d, r8d
mov eax, r8d
ret
Run Code Online (Sandbox Code Playgroud)
如:将结果存储在eax. 11.x 版本eax在函数开头将其归零,然后在函数体中对其进行修改,从而无需额外的mov指令。
iBu*_*Bug -2
先结论:这是GCC刻意的优化选择。
如果您在本地使用 GCC ( ) 而不是在 Godbolt 上,您可以看到两条指令之间gcc -O3 -S存在对齐指令ret:
; top part omitted
.L9:
ret
.p2align 4,,10
.p2align 3
.L6:
ret
.cfi_endproc
Run Code Online (Sandbox Code Playgroud)
反汇编后的目标文件在该填充区域中包含一个 NOP:
8: 75 13 jne 1d <get_steps_to_zero+0x1d>
a: eb 24 jmp 30 <get_steps_to_zero+0x30>
c: 0f 1f 40 00 nopl 0x0(%rax)
<...>
2b: 75 f0 jne 1d <get_steps_to_zero+0x1d>
2d: c3 ret
2e: 66 90 xchg %ax,%ax
30: c3 ret
Run Code Online (Sandbox Code Playgroud)
第二ret条指令与 16 字节边界对齐,而第一条指令则不然。这使得处理器在用作来自远程源的跳转目标时能够更快地加载指令。然而,后续的 Creturn语句与第一条指令足够接近ret,因此它们不会从跳转到对齐的目标中受益。
这种对齐方式在我的 Zen 2 CPU 上更加明显-mtune=native,添加了更多填充字节:
29: 75 f2 jne 1d <get_steps_to_zero+0x1d>
2b: c3 ret
2c: 0f 1f 40 00 nopl 0x0(%rax)
30: c3 ret
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
239 次 |
| 最近记录: |