在 -O3
优化级别下,gcc 会生成以下结果 (Godbolt compiler explorer):
int mul37(int a) { return a*37
leal (%rdi,%rdi,8), %eax # eax = a * 9
leal (%rdi,%rax,4), %eax # eax = a + 4*(a*9)
ret
这里使用的是37 = 9*4 + 1
,而不是通过第一个lea
破坏原始a
值,这样可以在第二个lea
中同时使用两个值。
不过你并不孤单,在这个问题上:即使是最近版本的clang(3.8及以上),通常也会使用2个lea
指令来替代imul
(例如对于*15
),但是它会忽略这个问题,使用如下代码:
imull $37, %edi, %eax
ret
它确实使用与gcc相同的模式执行
*21
,如
5*4+1
。 (clang3.6及更早版本始终使用
imul
,除非有单指令替代
shl
或
lea
)
ICC和MSVC也使用imul,但它们似乎不喜欢使用2个
lea
指令,因此
imul
是“故意”使用的。
请参见godbolt链接,以了解gcc7.2与clang5.0的各种乘数。尝试使用
gcc -m32 -mtune=pentium
甚至
pentium3
以查看gcc当时可以使用多少个指令更有趣。虽然P2 / P3的
imul r,r,i
具有4个周期延迟,但这有点疯狂。 Pentium具有9个周期的
imul
和没有OOO来隐藏延迟,因此努力避免它是有意义的。
mtune=silvermont
可能只愿意用单个指令替换32位
imul
,因为它具有3个周期延迟/ 1c吞吐量乘法,但是解码通常是瓶颈(根据Agner Fog的说法,
http://agner.org/optimize/)。您甚至可以考虑使用
imul $64,%edi,%eax
(或其他2的幂)而不是
mov
/
shl
,因为imul-immediate是复制和乘法。
具有讽刺意味的是,
gcc
错过了
* 45
的情况,并使用
imul
,而clang使用2个
lea
s。猜测现在是时候提交一些遗漏优化的错误报告了。如果2 LEA优于1 IMUL,则应尽可能使用它们。
较旧的clang(3.7及更早版本)除非单个
lea
足以完成任务,否则将使用
imul
。我没有查看更新日志以查看他们是否进行了基准测试来决定支持延迟还是吞吐量。
相关:
在不是地址/指针的值上使用LEA?关于为什么LEA使用内存操作数语法和机器编码的规范答案,即使它是一个移位+加法指令(并且在大多数现代微体系结构中在ALU上运行,而不是AGU)。
lea
将64位寻址模式截断为32位始终是安全的。https://dev59.com/kek6XIcBkEYKwwoYDPsR - Peter Cordes