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

时间:2016-06-20 14:30:50

标签: performance assembly x86-64 intel multiplication

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

int64_t calc(int64_t a) {
  return (a << 6) + (a << 16) - a;
}

大会:

  imul        rdx,qword ptr [a],1003Fh  

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

  mov         rbx,qword ptr [a]  
  mov         rax,rbx  
  shl         rax,6  
  mov         rcx,rbx  
  shl         rcx,10h  
  add         rax,rcx  
  sub         rax,rbx  

我一直认为乘法总是慢于几个班次/加法?现代英特尔x86_64处理器不再是这种情况吗?

1 个答案:

答案 0 :(得分:11)

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

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

来自Agner Fog's instruction tables的数据。另请参阅标记wiki中的其他内容。

现代CPU中的晶体管预算非常庞大,并且允许以这种低延迟进行64位乘法所需的硬件并行度。 (It takes a lot of adders制作large fast multiplier)。

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

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

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

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

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

除了仅需要单个LEA或移位的乘数外,

clang3.7及更早版本倾向于使用imul。所以clang最近改为优化延迟而不是通过小常数乘以吞吐量。 (或者可能出于其他原因,比如不与其他与竞争对手只在同一端口的东西竞争。)

e.g。 this code on the Godbolt compiler explorer

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及更高版本生成相同的代码。