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

Lea*_*ess 3 x86 assembly opcodes

你为什么要用:

MOV EAX, 22 
SHL EAX, 2
Run Code Online (Sandbox Code Playgroud)

...乘以4而不是仅仅使用MUL指令?
我知道这也可以用SHR而不是DIV.

这样做有什么好处?
你也可以用奇数做这个或者它只能是偶数吗?

Ira*_*ter 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
Run Code Online (Sandbox Code Playgroud)

将EAX乘以18:

   LEA  EAX, [EAX + 8*EAX]
   SHL  EAX, 1
Run Code Online (Sandbox Code Playgroud)

将EAX乘以7,将结果移至EBX:

   LEA  EBX, [8*EAX]
   SUB  EBX, EAX
Run Code Online (Sandbox Code Playgroud)

将EAX乘以28:

   LEA  EBX, [8*EAX]
   LEA  ECX, [EAX+4*EAX]  ; this and previous should be executed in parallel
   LEA  EAX, [EBX+4*ECX]
Run Code Online (Sandbox Code Playgroud)

乘以1020:

   LEA  ECX, [4*EAX]
   SHL  EAX, 10         ; this and previous instruction should be executed in parallel
   SUB  EAX, ECX
Run Code Online (Sandbox Code Playgroud)

乘以35

   LEA  ECX, [EAX+8*EAX]
   NEG  EAX             ; = -EAX
   LEA  EAX, [EAX+ECX*4]
Run Code Online (Sandbox Code Playgroud)

因此,当你想要达到乘以适度大小常数的效果时,你必须考虑如何将它"分解"到LEA指令可以产生的各种产品中,以及如何移动,添加或减去 a部分结果得到最终答案.

值得注意的是,通过这种方式可以产生多少乘法常数.您可能认为这仅适用于非常小的常量,但正如您从上面的1020示例中可以看到的那样,您也可以获得一些令人惊讶的中等大小的常量.当索引到结构数组时,事实证明这非常方便,因为你必须将索引乘以结构的大小.通常在索引这样的数组时,您需要计算元素地址并获取值; 在这种情况下,您可以将最终的LEA指令合并到MOV指令中,而不能使用真正的MUL.通过这种类型的习惯用法,可以购买额外的时钟周期来完成MUL.

[我已经构建了一个编译器,通过对指令组合进行小的穷举搜索,使用这些指令计算"最佳乘以常数"; 然后它缓存该答案以供以后重用].

  • `imul r,r/m,imm32`非常好看,作为一个mov-and-multiply.在现代英特尔CPU上,它只有3个周期延迟(即使是64位操作数大小),每个时钟吞吐量也有一个.但是,很多乘法常数可以在2个周期内完成,正如您通过指令级并行性的示例很好地演示的那样.gcc和clang做同样的事情.(clang-3.6和更老的人通常更喜欢IMUL,如果它只用一个LEA就无法完成工作,但是现代的clang倾向于延迟而不是gcc的指令/ uop计数.) (2认同)