为什么这一系列指令更快?

Ber*_*ach 12 c++ x86 assembly gcc clang

我正在比较GCC和Clang输出来评估浮点表达式,并偶然发现了我无法解释的性能差异.

源代码

float evaluate(float a, float b) {
    return (a - b + 1.0f) * (a - b) * (a - b - 1.0f);
}
Run Code Online (Sandbox Code Playgroud)

产生GCC 7.2(-std = c ++ 1y -O2)

evaluate(float, float):
  subss xmm0, xmm1
  movss xmm2, DWORD PTR .LC0[rip]
  movaps xmm1, xmm0
  addss xmm1, xmm2
  mulss xmm1, xmm0
  subss xmm0, xmm2
  mulss xmm0, xmm1
  ret
.LC0:
  .long 1065353216
Run Code Online (Sandbox Code Playgroud)

而Clang 5.0.0(-std = c ++ 1y -O2)产生了

.LCPI0_0:
  .long 1065353216 # float 1
.LCPI0_1:
  .long 3212836864 # float -1
evaluate(float, float): # @evaluate(float, float)
  subss xmm0, xmm1
  movss xmm1, dword ptr [rip + .LCPI0_0] # xmm1 = mem[0],zero,zero,zero
  addss xmm1, xmm0
  mulss xmm1, xmm0
  addss xmm0, dword ptr [rip + .LCPI0_1]
  mulss xmm0, xmm1
  ret
Run Code Online (Sandbox Code Playgroud)

GCC似乎更喜欢movapsmovss,即使movss是在这种情况下就足够了.正如Peter Cordes所指出的,这实际上更好,因为使用movaps避免了对XMM寄存器进行部分更新的停顿.

GCC使用三个而不是两个XMM寄存器.

Clang使用两个常量,只为它们添加,而不是仅使用一个并使用subss从寄存器中减去一个.

我绘制了这些序列的指令级真正依赖关系,而Clang的缩短了一级.

性能方面,与我的预期完全不同,GCC版本的速度提高了约30%.

我已经在Intel(Broadwell)和AMD(Bulldozer)CPU上进行了测试,我不明白为什么GCC代码会更快.

原始基准使用了错误的内联asm代码.在为感兴趣的函数的C++代码创建两个目标文件(一个使用GCC,一个使用Clang)并使用GCC链接它们之后,性能差异消失了.彼得科德斯暗示这可能是因为-O1省略了这些.p2align指令.

我查看了新基准测试方法的编译器输出,并断言两个实现都evaluate()具有与上面完全相同的代码,并且编译器实际上正在调用它们.