没错,现代 x86 CPU(尤其是 Intel)具有非常高性能的乘法器。
imul r, r/m
and imul r, r/m, imm
在 Intel SnB 系列和 AMD Ryzen 上,两者都是 3 个周期延迟,每 1c 吞吐量一个周期延迟,即使对于 64 位操作数大小也是如此。
在 AMD Bulldozer 系列上,延迟为 4c 或 6c,每 2c 延迟 1 次或每 4c 吞吐量 1 次。 (64 位操作数大小的速度较慢)。
数据来自Agner Fog 的说明书 http://agner.org/optimize/。另请参阅中的其他内容x86 /questions/tagged/x86标签维基。
现代 CPU 中的晶体管预算相当庞大,并允许以如此低的延迟执行 64 位乘法所需的硬件并行量。 (这需要一个lot加法器的数量 https://en.wikipedia.org/wiki/Dadda_multiplier做一个大快速乘法器 https://en.wikipedia.org/wiki/Binary_multiplier#Implementations. 现代 X86 处理器实际上如何计算乘法? https://stackoverflow.com/questions/26370287/how-modern-x86-processors-actually-compute-multiplications/62117473#62117473).
受到功率预算而不是晶体管预算的限制,意味着可以为许多不同的功能提供专用硬件,只要它们不能同时切换(https://en.wikipedia.org/wiki/Dark_silicon https://en.wikipedia.org/wiki/Dark_silicon)。例如你不能饱和pext
/pdep
单元、整数乘法器和向量 FMA 单元同时在 Intel CPU 上运行,因为它们中的许多单元位于相同的执行端口上。
有趣的事实:imul r64
也是 3c,因此您可以在 3 个周期内获得完整的 64*64 => 128b 乘法结果。imul r32
不过,是 4c 延迟和额外的 uop。我的猜测是,额外的微操作/周期将常规 64 位乘法器的 64 位结果分成两个 32 位一半。
编译器通常会针对延迟进行优化,并且通常不知道如何优化短的独立依赖链以提高吞吐量,而不是优化延迟瓶颈的长循环承载依赖链。
gcc 和 clang3.8 及更高版本最多使用两个LEA
指令而不是imul r, r/m, imm
。我认为 gcc 会使用imul
如果替代方案是 3 个或更多指令(不包括mov
), 尽管。
这是一个合理的调整选择,因为 3 指令 dep 链的长度与imul
关于英特尔。使用两条 1 周期指令会花费额外的 uop,将延迟缩短 1 周期。
clang3.7 及更早版本往往受到青睐imul
只需要单个 LEA 或移位的乘法器除外。因此,clang 最近改为针对延迟进行优化,而不是针对乘以小常数的吞吐量进行优化。 (或者可能是出于其他原因,例如不与仅与乘法器位于同一端口上的其他事物竞争。)
e.g. Godbolt 编译器浏览器上的这段代码 http://gcc.godbolt.org/#compilers:!((compiler:g6,options:'-xc+-std%3Dgnu11+-Wall+-Wextra+-fverbose-asm+-O3+-march%3Dhaswell',source:'int+foo+(int+a)+%7B+return+a+*+63%3B+%7D')),filterAsm:(commentOnly:!t,directives:!t,intel:!t,labels:!t),version:3:
int foo (int a) { return a * 63; }
# gcc 6.1 -O3 -march=haswell (and clang actually does the same here)
mov eax, edi # tmp91, a
sal eax, 6 # tmp91,
sub eax, edi # tmp92, a
ret
clang3.8 及更高版本生成相同的代码。