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";然后它会缓存该答案供以后重用。
为什么要使用:
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";然后它会缓存该答案供以后重用。