x86_64:IMUL快于2x SHL + 2x ADD吗?

rus*_*tyx 5 performance assembly x86-64 intel multiplication

/O2(发布)模式下查看由Visual Studio(2015U2)生成的程序集时,我看到这个"手动优化"的C代码被转换回乘法:

int64_t calc(int64_t a) {
  return (a << 6) + (a << 16) - a;
}
Run Code Online (Sandbox Code Playgroud)

部件:

  imul        rdx,qword ptr [a],1003Fh  
Run Code Online (Sandbox Code Playgroud)

所以我想知道这是否真的比按照它的编写方式更快,类似于:

  mov         rbx,qword ptr [a]  
  mov         rax,rbx  
  shl         rax,6  
  mov         rcx,rbx  
  shl         rcx,10h  
  add         rax,rcx  
  sub         rax,rbx  
Run Code Online (Sandbox Code Playgroud)

我总是觉得乘法总是比几个班次/加法慢?现代英特尔x86_64处理器不再是这种情况吗?

Pet*_*des 11

没错,现代的x86 CPU(尤其是Intel)具有非常高的性能乘数.
imul r, r/m并且imul r, r/m, imm是3周期延迟,Intel SnB系列和AMD Ryzen每1c吞吐量一个,即使是64位操作数大小.

在AMD Bulldozer系列中,它具有4c或6c延迟,每2c一个或每4c吞吐一个.(64位操作数大小的较慢时间).

来自Agner Fog指令表的数据.另请参阅标记wiki 中的其他内容.


现代CPU中的晶体管预算非常庞大,并且允许以这种低延迟进行64位乘法所需的硬件并行度.(制作大型快速乘法器需要很多加法).

受功率预算限制,而不是晶体管预算,意味着可以使用专用硬件来实现许多不同功能,只要它们不能同时切换(https://en.wikipedia.org/wiki/Dark_silicon)).例如,你不能在Intel CPU上同时使pext/ pdepunit,整数乘法器和向量FMA单元饱和,因为它们中的许多都位于相同的执行端口上.

有趣的事实:imul r64也是3c,所以你可以在3个周期内获得完整的64*64 => 128b乘法结果. imul r32但是,4c延迟和额外的uop.我的猜测是额外的uop/cycle将64位结果从常规64位乘法器分成两个32位半.


编译器通常会针对延迟进行优化,并且通常不知道如何优化短的独立依赖链以实现吞吐量,而不是长循环传输的依赖链,这是延迟的瓶颈.

gcc和clang3.8及以后使用最多两个LEA指令而不是imul r, r/m, imm.我认为imul如果替代方案是3个或更多指令(不包括mov),gcc将使用.

这是一个合理的调整选择,因为3指令dep链的长度与imulIntel上的相同.使用两个1周期指令花费额外的uop来将延迟缩短1个周期.

imul除了仅需要单个LEA或移位的乘数外,clang3.7和更早版本倾向于偏向.所以clang最近改为优化延迟而不是通过小常数乘以吞吐量.(或者可能出于其他原因,比如不与其他与竞争对手相同的其他东西竞争.)

例如,Godbolt编译器浏览器上的这段代码:

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
Run Code Online (Sandbox Code Playgroud)

clang3.8及更高版本生成相同的代码.

  • @rustyx:IDK.通过管道跟踪4个ALU uop并将它们的结果转发给彼此比跟踪一个需要更多的能量,这可能比运行乘法器花费更多!乱序执行很昂贵.我希望我知道这些数字是什么,所以我可以猜到答案,但我没有做任何功率优化(除了明显的东西,比如使用128位AVX矢量操作,当我不需要上面的128结果). (2认同)