MUL/DIV 指令与 MOV & SHL/SHR (Pentium Pro)

MUL/DIV instructions vs. MOV & SHL/SHR (Pentium Pro)

为什么要使用:

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。您可以使用快速指令(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]

所以,当你想实现乘以一个适度大小的常量的效果时,你就得想一想如何把它"factored"变成LEA指令可以产生的各种乘积,以及怎么可能移动、添加、 或减去 部分结果以获得最终答案。

通过这种方式可以产生多少乘以常数,这很了不起。 您可能认为这仅对非常小的常量有用,但正如您从上面的 1020 示例中看到的那样,您也可以获得一些令人惊讶的中等大小的常量。这在索引结构数组时非常方便,因为您必须将索引乘以结构的大小。 通常在像这样索引数组时,您需要计算元素地址并获取值;在这种情况下,您可以将最终的 LEA 指令合并到 MOV 指令中,而这对于真正的 MUL 是做不到的。这为您购买了额外的时钟周期,您可以在其中通过此类习语执行 MUL。

[我构建了一个编译器,通过对指令组合进行小型详尽搜索,使用这些指令计算 "best multiply by constant";然后它会缓存该答案供以后重用。