假设 %edi 包含 x 并且我想仅使用 2 个连续的 leal 指令以 37*x 结束,我该怎么做?
例如,要获得 45 倍,您会这样做
leal (%edi, %edi, 8), %edi
leal (%edi, %edi, 4), %eax (to be returned)
我一生都无法弄清楚用什么数字代替 8 和 4,这样结果 (%eax) 将是 37x
假设 %edi 包含 x 并且我想仅使用 2 个连续的 leal 指令以 37*x 结束,我该怎么做?
例如,要获得 45 倍,您会这样做
leal (%edi, %edi, 8), %edi
leal (%edi, %edi, 4), %eax (to be returned)
我一生都无法弄清楚用什么数字代替 8 和 4,这样结果 (%eax) 将是 37x
在-O3
,gcc 将发出(Godbolt 编译器资源管理器):
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
,而不是使用第一个破坏原始a
值,lea
因此它可以在第二个中使用两者。
不过,您在没有发现这个方面很有好处:最近的 clang(3.8 和更高版本)通常会使用 2 个lea
指令而不是imul
(例如 for *15
),但它错过了这个并使用:
imull $37, %edi, %eax
ret
它确实*21
与 gcc 使用的模式相同,如5*4 + 1
. (clang3.6 和更早版本总是使用imul
,除非有单指令替代shl
或lea
)
ICC 和 MSVC 也使用 imul,但他们似乎不喜欢使用 2lea
条指令,所以imul
那里是“故意”的。
有关 gcc7.2 与 clang5.0 的各种乘法器,请参阅godbolt 链接。尝试gcc -m32 -mtune=pentium
甚至pentium3
查看当时 gcc 还愿意使用多少指令是很有趣的。虽然 P2/P3 有 4 个周期的延迟imul r, r, i
,所以这有点疯狂。Pentium 有 9 个周期imul
,没有 OOO 来隐藏延迟,因此尽量避免它是有意义的。
mtune=silvermont
应该只愿意imul
用一条指令替换 32 位,因为它具有 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 及以上)使用imul
,除非一个单一lea
的可以解决问题。我还没有查看更改日志以查看他们是否进行了基准测试以决定优先考虑延迟而不是吞吐量。
相关:在不是地址/指针的值上使用 LEA?关于为什么 LEA 使用内存操作数语法和机器编码的规范答案,即使它是一个 shift+add 指令(并且在大多数现代微架构中运行在 ALU 上,而不是 AGU 上。)