我想记住编译器如何执行整数除以2。有趣的是,我发现了一个我不理解的特殊行为:
https://godbolt.org/z/87n3x5Gjv
int div2(int x)
{
return x / 2;
}
字符串
第一个月
div2(int):
mov eax, edi
shr eax, 31
lea eax, [rax+rdi]
sar eax
ret
型
我个人会将lea
与add
交换,-O 0和-O2可以做到这一点,clang也可以在任何级别上做到这一点。这与add
指令可能修改的标志有关吗?我很想知道为什么会发生这样的事情,提前感谢!
1条答案
按热度按时间m3eecexj1#
我认为这里的莱亚更糟糕,可能是一些启发式错误的结果。
我也注意到了这一点,有些编译器似乎毫无理由地更喜欢莱亚,即使它们不需要将结果复制到不是两个输入的目的地。
莱亚在某些CPU上运行的端口更少,例如Ice Lake之前的Intel。至少它在所有现代CPU上仍然是一个“简单”的LEA,2个组件,索引上没有比例因子。否则它可以在更少的端口上运行,并且可能具有高于1个周期的延迟。(https://uops.info/)(但是使用更复杂的莱亚完成2到4条指令的工作通常仍然是值得的,除非它是循环承载依赖链的一部分。
在寻址模式下,莱亚确实会为SIB字节花费额外的代码大小。(对于3字节
lea
,操作码+ ModRM + SIB;对于2字节add eax, edi
,仅操作码+ ModRM)不写FLAGS在这里没有用。x86 CPU完全有效地处理FLAGS写,即使对FLAGS和整数结果使用相同的物理寄存器文件条目。(Sandybridge-family绝对可以做到这一点;我假设Zen和其他人也可以做到这一点,而不是需要另一个完整的寄存器文件和寄存器分配表或其他东西,或者需要uops有2个输出。我似乎记得有一个关于GCC bugzilla问题的评论,建议不修改FLAGS会有一些好处,比如CPU的工作量减少,不必重命名它。
当你已经写了一个寄存器目标,写FLAGS也没有额外的成本。它甚至可以释放一个旧的物理寄存器,它只保存FLAGS结果,而不是任何整数寄存器的当前值。