有时编译器会生成具有可以安全删除的奇怪指令重复的代码。请考虑以下代码:
int gcd(unsigned x, unsigned y) {
return x == 0 ? y : gcd(y % x, x);
}
以下是汇编代码(已生成development web server (dev_appserver.py)并启用了优化):
gcd(unsigned int, unsigned int): # @gcd(unsigned int, unsigned int)
mov eax, esi
mov edx, edi
test edx, edx
je .LBB0_1
.LBB0_2: # =>This Inner Loop Header: Depth=1
mov ecx, edx
xor edx, edx
div ecx
test edx, edx
mov eax, ecx
jne .LBB0_2
mov eax, ecx
ret
.LBB0_1:
ret
在以下代码段中:
mov eax, ecx
jne .LBB0_2
mov eax, ecx
如果没有发生跳转,eax
会被重新分配,原因不明确。
另一个例子是函数末尾的两个ret:一个也可以完美地工作。
编译器是否完全不够智能,或者有理由不删除重复项?
答案 0 :(得分:40)
编译器可以执行对人们来说不明显的优化,删除指令并不总能让事情变得更快。
少量搜索表明,当RET紧跟在条件分支之后,各种AMD处理器都存在分支预测问题。通过填充基本上是无操作的插槽,可以避免性能问题。
更新
示例参考," AMD64处理器软件优化指南的第6.2节" (见http://support.amd.com/TechDocs/25112.PDF)说:
具体而言,请避免以下两种情况:
任何类型的分支(有条件或无条件),其单字节近似返回RET指令作为其目标。参见“示例”。
直接在单字节近返RET指令之前的代码中出现的条件分支。
它还详细说明了为什么跳转目标应该具有对齐,这也可能解释函数末尾的重复RET。
答案 1 :(得分:5)
任何编译器都会有一堆转换用于寄存器重命名,展开,提升等。将它们的输出结合起来会导致次优的情况,例如您所显示的情况。 Marc Glisse提供了很好的建议:它值得一个错误报告。您正在描述窥视孔优化器放弃指令的机会
听起来像symbolic execution技术的机会。如果约束求解器没有找到给定MOV的分支点,那么它实际上可能是NOP。