为什么要使用:
MOV EAX, 22
SHL EAX, 2
...当乘以 4 而不是仅使用MUL
指令时?
我知道这也可以用SHR
代替来完成DIV
。
这样做有什么好处?
你也可以用奇数来做这个,还是只能是偶数?
有许多代码习惯用法比“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。
[我建立了一个编译器,通过对指令组合进行小而详尽的搜索,使用这些指令计算“最佳乘以常数”;然后它会缓存该答案以供以后重用]。
一般来说,使用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