3

为什么要使用:

MOV EAX, 22 
SHL EAX, 2

...当乘以 4 而不是仅使用MUL指令时?
我知道这也可以用SHR代替来完成DIV

这样做有什么好处?
你也可以用奇数来做这个,还是只能是偶数?

4

2 回答 2

5

有许多代码习惯用法比“MUL 常量”更快。

现代 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]

所以,当你想达到乘以一个适度的大小常数的效果时,你必须考虑如何将它“分解”到 LEA 指令可以产生的各种乘积中,以及如何移动、添加或减去一个部分结果得到最终答案。

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

[我建立了一个编译器,通过对指令组合进行小而详尽的搜索,使用这些指令计算“最佳乘以常数”;然后它会缓存该答案以供以后重用]。

于 2016-12-04T09:26:40.613 回答
3

一般来说,使用SHL/指令比/快得多。SHRMULDIV

要回答您的第二个问题,您也可以使用奇数执行此操作,但您必须添加另一条指令。因此,从技术上讲,您不能只使用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
于 2016-12-03T20:56:24.267 回答