我想知道是否有一种无需使用MUL或DIV指令即可执行任何乘法或除法的方法,因为它们需要大量的CPU周期。我可以针对该目标利用SHL或SHR指令吗?如何实现汇编代码?
最佳答案
就像汇编中的所有其他内容一样,有很多方法可以进行乘法和除法。
lea
的地址计算选项(仅乘法)。 神话破坏
MUL
和IMUL
在现代CPU上非常快,请参见:http://www.agner.org/optimize/instruction_tables.pdfDIV
和IDIV
一直非常缓慢。英特尔Skylake的示例(第217页):
请注意,这是最大延迟要乘以两个64!位值。
如果CPU要做的只是乘法,则每个CPU周期可以完成这些乘法之一。
如果您认为上述示例使用移位并加乘以7的延迟时间为4个周期(使用lea则为3个周期)。没有真正的方法可以击败现代CPU上的普通乘法。
乘以倒数
根据Agner Fog's asm lib instruction page 12:
当您需要除以常数或连续多次除以相同的变量时,乘以倒数非常有效。
您可以在Agner Fog's assembly library中找到展示该概念的非常酷的汇编代码。
移位和添加/订阅
右移是除以两个
shr
-( R 得出)。左移是两个
shl
的乘积-( L arger)。您可以在进行加和减运算时校正非2的幂。
//Multiply by 7
mov ecx,eax
shl eax,3 //*8
sub eax,ecx //*7
使用该方法的除以2的幂以外的除法很快变得复杂。
您可能想知道为什么我会以奇怪的顺序进行操作,但是我试图使dependency chain尽可能短,以使可以并行执行的指令数量最大化。
使用
Lea
Lea是计算地址偏移量的指令。
它可以在一条指令中计算2、3、4、5、8和9的倍数。
像这样:
//Latency on AMD CPUs (K10 and later, including Jaguar and Zen)
//On Intel all take 1 cycle.
lea eax,[eax+eax] //*2 1 cycle
lea eax,[eax*2+eax] //*3 2 cycles
lea eax,[eax*4] //*4 2 cycles more efficient: shl eax,2 (1 cycle)
lea eax,[eax*4+eax] //*5 2 cycles
lea eax,[eax*8] //*8 2 cycles more efficient: shl eax,3 (1 cycle)
lea eax,[eax*8+eax] //*9 2 cycles
但是请注意,带乘数(比例因子)的
lea
在从K10到Zen的AMD CPU上被视为“复杂”指令,并且具有2个CPU周期的延迟。在较早的AMD CPU(k8)上,即使使用简单的lea
或[reg+reg]
寻址模式,[reg+disp8]
始终具有2个周期的延迟。AMD
Agner Fog的指令表不适用于AMD Zen:根据InstLatx64(http://instlatx64.atw.hu/),三分量或缩放索引LEA在Zen上仍为2个周期(每个时钟吞吐量只有2个周期,而不是4个)。同样,像早期的CPU一样,在64位模式下,
lea r32, [r64 + whatever]
具有2个周期的延迟。因此,在AMD CPU上使用lea rdx, [rax+rax]
而不是lea edx, [rax+rax]
实际上更快,这与Intel的将结果截断为32位是免费的不同。* 4和* 8可以使用
shl
更快地完成,因为简单的移位仅需一个周期。从好的方面来说,
lea
不会更改标志,它允许自由移动到另一个目标寄存器。因为
lea
只能向左移位0、1、2或3位(也就是乘以1、2、4或8),所以这是您得到的唯一中断。英特尔
在Intel CPU(Sandybridge系列)上,任何2组件LEA(仅一个
+
)都具有单周期延迟。因此,lea edx, [rax + rax*4]
具有单周期延迟,但是lea edx, [rax + rax + 12]
具有3个周期延迟(并且吞吐量更差)。在C++ code for testing the Collatz conjecture faster than hand-written assembly - why?中详细讨论了这种折衷的示例。关于assembly - 程序集8086-在没有MUL和DIV指令的情况下实现任何乘法和除法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27922579/