Assembly MUL/DIV指令与MOV&;SHL/SHR(奔腾Pro)

Assembly MUL/DIV指令与MOV&;SHL/SHR(奔腾Pro),assembly,x86,opcodes,Assembly,X86,Opcodes,您为什么要使用: MOV EAX, 22 SHL EAX, 2 …当乘以4时,而不是仅使用MUL指令? 我知道这也可以通过SHR而不是DIV实现 这样做有什么好处? 您也可以使用奇数或偶数执行此操作吗?使用SHL/SHR指令通常比MUL/DIV快得多 要回答第二个问题,您也可以使用奇数,但您必须添加另一条指令。因此,从技术上讲,您不能只使用SHL/SHR 例如:以下代码在不使用MUL指令的情况下乘以5: mov num, 5 mov eax, num mov ebx, num shl eax

您为什么要使用:

MOV EAX, 22 
SHL EAX, 2
…当乘以4时,而不是仅使用
MUL
指令?
我知道这也可以通过
SHR
而不是
DIV
实现

这样做有什么好处?

您也可以使用奇数或偶数执行此操作吗?

使用
SHL
/
SHR
指令通常比
MUL
/
DIV
快得多

要回答第二个问题,您也可以使用奇数,但您必须添加另一条指令。因此,从技术上讲,您不能只使用
SHL
/
SHR

例如:以下代码在不使用
MUL
指令的情况下乘以5:

mov num, 5
mov eax, num
mov ebx, num
shl eax, 2    ; MULs by 4
add eax, ebx  ; ADD the x1 to make = 5

有许多代码习惯用法比“MUL constant”更快

现代x86 CPU至少在几个时钟内执行MUL。因此,任何在1-2个时钟内计算乘积的代码序列都将优于MUL。您可以使用fast指令(ADD、SHL、LEA、NEG)以及处理器可以在单个时钟中并行执行其中一些指令来代替MUL的事实。可以说,这意味着您可以在2个时钟内以多种组合执行其中4条指令,如果您避免某些数据依赖性的话

LEA指令特别有趣,因为它可以乘以一些小常数(1,2,3,4,5,8,9)并将乘积移动到另一个寄存器,这是打破数据依赖关系的一种简单方法。这允许您在不破坏原始操作数的情况下计算子乘积

一些例子:

将EAX乘以5,将乘积移动到ESI:

   LEA ESI, [EAX+4*EAX]    ; this takes 1 clock
将EAX乘以18:

   LEA  EAX, [EAX + 8*EAX]
   SHL  EAX, 1
将EAX乘以7,将结果移至EBX:

   LEA  EBX, [8*EAX]
   SUB  EBX, EAX
将EAX乘以28:

   LEA  EBX, [8*EAX]
   LEA  ECX, [EAX+4*EAX]  ; this and previous should be executed in parallel
   LEA  EAX, [EBX+4*ECX]
乘以1020:

   LEA  ECX, [4*EAX]
   SHL  EAX, 10         ; this and previous instruction should be executed in parallel
   SUB  EAX, ECX
乘以35

   LEA  ECX, [EAX+8*EAX]
   NEG  EAX             ; = -EAX
   LEA  EAX, [EAX+ECX*4]
因此,当您想要实现乘以适度大小常数的效果时,您必须考虑如何将其“分解”到LEA指令可以生成的各种产品中,以及如何移动、添加或减去部分结果以获得最终答案

值得注意的是,有多少乘以常数可以通过这种方式产生。 您可能认为这只对非常小的常量有用,但正如您从上面的1020示例中看到的,您也可以得到一些中等大小的常量。这在索引到结构数组时非常方便,因为必须将索引乘以结构的大小。 通常,在为这样的数组编制索引时,需要计算元素地址并获取值;在这种情况下,您可以将最终LEA指令合并到MOV指令中,这是实际MUL无法做到的。这为您购买了额外的时钟周期,在这些时钟周期中,您可以使用这种习惯用法执行MUL


[我已经构建了一个编译器,通过对指令组合进行小范围的穷举搜索,使用这些指令计算“最佳乘以常数”;然后将答案缓存起来,以便以后重用]。

移位的周期数取决于cpu型号,但很长一段时间以来它不是每位1个时钟(如果曾经是的话)。他也没有问到乘以5的问题,而您在那里使用了
ADD
:80186上的POnly每移位一位就要花费1个周期。在8086上,它每位花费4个周期,并且它不支持移位计数的立即操作数,就像您在这里使用的那样。80286和更高版本的CPU都有桶形移位器,可以在一个周期内执行任何大小的移位。现代的无序CPU可以同时进行两次移位,有效地将移位成本降低到半个周期。@ninjalj不,奔腾4也不例外。由于奔腾4还有一个桶形移位器,所以无论移位的位数多少,延迟都是恒定的。在许多旧CPU上有额外的开销,但是转换本身只需要一个周期。自286年以来,移位指令的成本并不取决于移位的位数。是的,我读过Darek的所有文章。他们并不是特别权威,他以不严谨的言辞著称,但他仍然是一个聪明人,如果你有时间并且感兴趣的话,关于x86体系结构的相关文章值得一读。但这个特殊的问题是我见过的一个经过多个来源验证的问题,所以我很好奇为什么罗斯·里奇的说法正好相反。也许他知道一些我不知道的事情。@CodyGray我没有消息来源,这根本没有意义。也许这是一个缓慢的桶式移位器,传播延迟将执行时间推过一个周期,但对我来说更可能的解释是延迟来自其他地方。我怀疑这与Pentium 4(pre-Prescott)上的移位指令有关,该指令在Agner Fog表的“mmxsh”子单元上被列为执行指令。这对我来说意味着,额外的循环是使用MMX桶形移位器进行整数移位的结果,类似于额外的循环MUL/DIV,因为它们是使用FP单位执行的。在基数10中,左/右移位乘以10的幂远快于进行实乘法(没有人这样做)。同样的道理也适用于在任何基础上乘以基础的幂。要了解更多有关asm中的快速功能的信息,请参阅,尤其是。另请参阅与DIV相比,shift和LEA的准确速度。现代Intel CPU具有极高的乘法硬件性能(例如,3周期延迟,每1c吞吐量一个
imul r64,r64
),但立即移位速度更快(1c延迟,每时钟两个tput)。为什么“奔腾Pro”在这个问题中扮演重要角色?a) 问题主体中没有提到,b)它们早已过时,c)答案相对稳定,对现代建筑很有用。从问题标题中删除?
imulr,r/m,imm32
作为mov和multiply非常好。在现代Intel CPU上