我正在比较GCC和Clang输出以评估浮点表达式,并偶然发现了我无法解释的性能差异。
源代码
float evaluate(float a, float b) {
return (a - b + 1.0f) * (a - b) * (a - b - 1.0f);
}
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
虽然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
GCC似乎更喜欢movaps
而不是movss
,即使在这种情况下movss
就足够了。正如Peter Cordes所指出的,这实际上更好,因为使用movaps
可以避免因对XMM寄存器的部分更新而失速。
GCC使用三个而不是两个XMM寄存器。
Clang使用两个常量并且只为它们添加,而不是仅使用一个并使用subss从寄存器中减去一个。
我绘制了这些序列的指令级真正依赖关系,而Clang的缩短了一级。
性能方面,与我的预期完全不同, GCC版本的速度提高了约30%。
我在Intel(Broadwell)和AMD(Bulldozer)CPU上测试了这个,我不明白为什么GCC代码会更快。
原始基准使用了错误的内联asm代码。在为感兴趣的函数的C ++代码创建两个目标文件(一个使用GCC,一个使用Clang)并使用GCC链接它们之后,性能差异消失了。 Peter Cordes暗示这可能是因为{{ 1}}省略-O1
指令。
我查看了新基准测试方法的编译器输出,并断言.p2align
的两个实现都具有与上面完全相同的代码,并且编译器实际上正在调用它们。