如何在x86中仅使用2个连续的leal指令将寄存器乘以37?

New*_*e18 5 x86 assembly x86-64 multiplication strength-reduction

假设%edi包含x并且我想仅使用2个连续的leal指令结束37*x,我将如何进行此操作?

例如,你可以做到45倍

leal (%edi, %edi, 8), %edi   
leal (%edi, %edi, 4), %eax (to be returned)
Run Code Online (Sandbox Code Playgroud)

我不能为我的生活找出代替8和4的数字,以便结果(%eax)将是37x

Pet*_*des 9

-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
Run Code Online (Sandbox Code Playgroud)

这是使用37 = 9*4 + 1,而不是a用第一个破坏原始值,lea所以它可以在第二个使用.

尽管如此,你最好不要发现这个:最近的clang(3.8和更新)通常会使用2个lea指令而不是imul(例如for *15),但它错过了这个并使用:

    imull   $37, %edi, %eax
    ret
Run Code Online (Sandbox Code Playgroud)

它确实*21与gcc使用的模式相同,如5*4 + 1.(clang3.6及更早版本总是使用,imul除非有单指令替代shllea)

ICC和MSVC也使用imul,但他们似乎不喜欢使用2 lea条指令,所以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秒.猜猜是时候提交一些遗漏优化错误报告了. 如果 2个LEA优于1 IMUL,则应尽可能使用它们.

较旧的铿锵声(3.7岁及以上)使用imul除非一个人lea能做到这一点.我没有查看更改日志,看看他们是否做了基准来决定支持延迟而不是吞吐量.


相关:对不是地址/指针的值使用LEA?关于为什么LEA使用内存操作数语法和机器编码的规范性答案,即使它是一个shift + add指令(并且在大多数现代微体系结构中运行在ALU而不是AGU上).


归档时间:

查看次数:

1302 次

最近记录:

7 年,4 月 前