我用x86-64程序集开发了一个程序,该程序需要通过相同的操作进行多次迭代:
IMUL rdx, 3 # rdx is always different
但是,我需要使运行时更快,因此我从上面想到了对该特定行的优化:
MOV rcx, rdx
SHL rdx, 1
ADD rdx, rcx
现在我问你们:这种修改会改善程序的运行时间(减少时钟),还是我应该坚持使用IMUL
命令?
答案 0 :(得分:5)
与lea rdx, [rdx + rdx*2]
相比,两者都很糟糕,使用缩放索引寻址模式获得总计*3
,这就是为什么如果您要求编译器始终使用LEA的原因他们来编译像这样的函数
long foo(long x){ return x * 3; }
(https://godbolt.org/z/6p4ynV)
LEA是一种通过x86寻址模式无馈送任意数字的方法,该方法使用结果进行加载或存储,只需将其放入寄存器即可。 Using LEA on values that aren't addresses / pointers?
在所有现代x86 CPU上,LEA都是单个uop。 唯一的问题是比其他方法好多少。 imul
也是1 uop,但是mov + shl + add的前端是3 。 (在所有仍然相关的主流和低功耗英特尔/ AMD上都是如此。请参见https://agner.org/optimize/。)64位imul
在某些较旧的微体系结构(例如Bulldozer系列和Silvermont / Goldmont)上特别慢,或者特别是较旧的Atom。
在AMD CPU(Bulldozer / Ryzen)上,它具有缩放的索引,因此它是“复杂的” LEA,并且具有2个周期延迟(Ryzen上imul
的延迟为3,或者在Bulldozer系列上更糟) 64位imul
较慢且未完全流水线化)。在Ryzen上,此LEA仍具有每时钟2个吞吐量。
在Intel CPU上,它只有2个组件(一个+
),因此它是“简单” LEA,具有 1个周期延迟,并且可以每2个运行一次。时钟吞吐量。因此,与一条shl
指令所花费的成本大致相同,但运行在不同的端口上。
(或者在Ice Lake上,因为它们将LEA单元添加到其他2个整数ALU端口中,所以每4个小时一次。所以它的价格与Ice Lake上一个add
一样便宜。)
您只想要mov
; shl
; sub
或add
,当n > 3
的乘数为2 ^ n + -1时。然后值得考虑imul
在延迟和前端吞吐量成本之间进行权衡。
通过移位原始寄存器,即使没有mov
消除的CPU(在IvyBridge和Ryzen之前)也可以以2个周期的延迟关键路径长度运行mov / shl / add序列。
也相关:C++ code for testing the Collatz conjecture faster than hand-written assembly - why?详细介绍了*3
相对于LEA优化的问题。
其他相关: