Assembly x86与3的乘法:IMUL与SHL+ADD

Assembly x86与3的乘法:IMUL与SHL+ADD,assembly,x86,x86-64,intel,micro-optimization,Assembly,X86,X86 64,Intel,Micro Optimization,我在x86-64汇编中开发了一个程序,需要通过相同的操作多次迭代: IMUL rdx, 3 # rdx is always different 但是,我需要加快运行速度,因此我从上面考虑了对特定行的优化: MOV rcx, rdx SHL rdx, 1 ADD rdx, rcx 现在我问你们:这种修改会提高无程序时钟的运行时间吗,还是我应该坚持使用IMUL命令?与lea-rdx相比,两者都很糟糕,[rdx+rdx*2],使用缩放索引寻址模式得到总共*3,这就是为什么如果你要求编译器编译这

我在x86-64汇编中开发了一个程序,需要通过相同的操作多次迭代:

IMUL rdx, 3   # rdx is always different
但是,我需要加快运行速度,因此我从上面考虑了对特定行的优化:

MOV rcx, rdx
SHL rdx, 1
ADD rdx, rcx
现在我问你们:这种修改会提高无程序时钟的运行时间吗,还是我应该坚持使用IMUL命令?

与lea-rdx相比,两者都很糟糕,[rdx+rdx*2],使用缩放索引寻址模式得到总共*3,这就是为什么如果你要求编译器编译这样的函数,编译器总是使用lea

long-Faunong x{return x*3;}

LEA是一种通过x86寻址模式提供任意数字的方法,无需将结果用于加载或存储,只需将其放入寄存器

在所有现代x86 CPU上,LEA是单个uop。唯一的问题是它比其他选择好多少。imul也是1 uop,但前端的mov+shl+add是3。这在所有仍然相关的主流和低功耗Intel/AMD中都是如此。请看,64位imul在一些较旧的微体系结构上特别慢,如推土机系列和Silvermont/Goldmont,尤其是较旧的Atom

在AMD CPU推土机/Ryzen上,它有一个缩放索引,因此它是一个复杂的LEA,与Ryzen上的imul相比,它有2个周期延迟,而Ryzen上的imul有3个周期延迟,或者在推土机系列上更糟,因为64位imul速度较慢,并且没有完全流水线。在Ryzen上,此LEA仍具有每时钟2个吞吐量

在Intel CPU上,它只有2个组件1+,因此它是一个简单的LEA,具有1个周期的延迟,可以以每时钟2个吞吐量运行。因此,与一条shl指令的成本大致相同,但运行在不同的端口上

或者在冰湖上,每时钟4个,因为他们将LEA单元添加到其他2个整数ALU端口。所以它和冰湖上的一种添加剂一样便宜

你只需要mov;shl;当乘数为2^n+-1(n>3)时,进行细分或添加。然后值得考虑imul在延迟和前端吞吐量成本之间进行权衡

通过移动原始寄存器,即使在IvyBridge和Ryzen之前未消除mov的CPU也可以以2周期延迟关键路径长度运行mov/shl/add序列

同样相关:有一些关于*3与LEA优化问题的细节

其他有关:

与lea rdx[rdx+rdx*2]相比,这两种方法都很糟糕,使用缩放索引寻址模式可以得到总共*3的结果,这就是为什么如果您要求编译器编译类似的函数,编译器总是使用lea的原因

long-Faunong x{return x*3;}

LEA是一种通过x86寻址模式提供任意数字的方法,无需将结果用于加载或存储,只需将其放入寄存器

在所有现代x86 CPU上,LEA是单个uop。唯一的问题是它比其他选择好多少。imul也是1 uop,但前端的mov+shl+add是3。这在所有仍然相关的主流和低功耗Intel/AMD中都是如此。请看,64位imul在一些较旧的微体系结构上特别慢,如推土机系列和Silvermont/Goldmont,尤其是较旧的Atom

在AMD CPU推土机/Ryzen上,它有一个缩放索引,因此它是一个复杂的LEA,与Ryzen上的imul相比,它有2个周期延迟,而Ryzen上的imul有3个周期延迟,或者在推土机系列上更糟,因为64位imul速度较慢,并且没有完全流水线。在Ryzen上,此LEA仍具有每时钟2个吞吐量

在Intel CPU上,它只有2个组件1+,因此它是一个简单的LEA,具有1个周期的延迟,可以以每时钟2个吞吐量运行。因此,与一条shl指令的成本大致相同,但运行在不同的端口上

或者在冰湖上,每时钟4个,因为他们将LEA单元添加到其他2个整数ALU端口。所以它和冰湖上的一种添加剂一样便宜

你只需要mov;shl;当乘数为2^n+-1(n>3)时,进行细分或添加。然后值得考虑imul在延迟和前端吞吐量成本之间进行权衡

通过移动原始寄存器,即使在IvyBridge和Ryzen之前未消除mov的CPU也可以以2周期延迟关键路径长度运行mov/shl/add序列

同样相关:有一些关于*3与LEA优化问题的细节

其他有关:


它不起作用,添加rdx,rdx只是再次加倍rdx,而不是添加原始rdx,shl rdx,2乘以4,但是lea rdx怎么样,[rdx+rdx*2]?我已经多次看到使用lea的这个解决方案,但我没有完全理解它。能否澄清一下?还有,这会不会是一种提高性能的方法?lea,尽管看起来像内存访问,但它不是内存访问。谢谢!我也更新了这个问题,但我想现在这个问题就更不理想了。我将看一看解释。@AndreasAbel:你是对的,没有接受立即数的2操作数形式,但许多汇编器所做的是从2个操作数生成3操作数形式。因此,imul rdx,3被编码为imul rdx,rdx,3指令将被编码
由于48 6b d2 03不起作用,添加rdx,rdx只是再次将rdx加倍,而不是添加原始rdx,shl rdx,2乘以4,但是lea rdx如何,[rdx+rdx*2]?我已经多次看到使用lea的此解决方案,但我没有完全理解它。能否澄清一下?还有,这会不会是一种提高性能的方法?lea,尽管看起来像内存访问,但它不是内存访问。谢谢!我也更新了这个问题,但我想现在这个问题就更不理想了。我将看一看解释。@AndreasAbel:你是对的,没有接受立即数的2操作数形式,但许多汇编器所做的是从2个操作数生成3操作数形式。因此,imul rdx,3被编码为imul rdx,rdx,3指令将被编码为48 6b d2 03感谢您的详细回答!还有一个小问题:在这个项目中,我也使用IDIV命令,但有两个原因:提取取整为较小整数的数字和使用rdx提取模。是否有任何可能的优化,或者该命令应按原样使用?@CosminAprodu:再次询问编译器。是的,乘法逆比除法快得多。给定商,你必须乘和减才能得到余数。查看编译器输出,了解它是如何完成的,特别是对于符号处理比较棘手的有符号整数。感谢您提供详细的答案!还有一个小问题:在这个项目中,我也使用IDIV命令,但有两个原因:提取取整为较小整数的数字和使用rdx提取模。是否有任何可能的优化,或者该命令应按原样使用?@CosminAprodu:再次询问编译器。是的,乘法逆比除法快得多。给定商,你必须乘和减才能得到余数。查看编译器输出,了解它是如何完成的,特别是对于符号处理比较棘手的有符号整数。