C++ 为什么这个指令序列更快?
我在比较GCC和clangoutput对浮点表达式的求值时,偶然发现了一个无法解释的性能差异 源代码C++ 为什么这个指令序列更快?,c++,gcc,assembly,x86,clang,C++,Gcc,Assembly,X86,Clang,我在比较GCC和clangoutput对浮点表达式的求值时,偶然发现了一个无法解释的性能差异 源代码 float evaluate(float a, float b) { return (a - b + 1.0f) * (a - b) * (a - b - 1.0f); } GCC 7.2(-std=c++1y-O2)生产 而Clang5.0.0(-std=c++1y-O2)产生了 GCC似乎更喜欢movaps而不是movss,即使在这种情况下movss就足够了。正如所指出的,这实际上
float evaluate(float a, float b) {
return (a - b + 1.0f) * (a - b) * (a - b - 1.0f);
}
GCC 7.2(-std=c++1y-O2)生产
而Clang5.0.0(-std=c++1y-O2)产生了
GCC似乎更喜欢movaps
而不是movss
,即使在这种情况下movss
就足够了。正如所指出的,这实际上比使用movaps
避免XMM寄存器的部分更新暂停更好
GCC使用三个而不是两个XMM寄存器
Clang使用两个常量并仅为其添加,而不是仅使用一个常量并使用subss从寄存器中减去一个常量
我画出了这些序列的指令级真实依赖关系,而Clang的则短了一级
性能方面,与我预期的完全不同,GCC版本大约快30%
我已经在Intel(Broadwell)和AMD(推土机)CPU上测试过,我不明白为什么GCC代码在这里会更快
原始基准使用了错误的内联asm代码。在创建了两个对象文件(一个带有GCC,一个是Clang),用于感兴趣的函数的C++代码,并用GCC链接到它们时,性能差异消失了。< /强> Peter Cordes暗示这可能是因为<代码> -O1 < /C> >省去<代码> .2AlcIn < /Cord>指令。
我查看了新基准测试方法的编译器输出,并断言
evaluate()
的两个实现具有与上述完全相同的代码,编译器实际上正在调用它们。您使用了哪些优化标志来生成gcc/clang输出?gcc更彻底地利用寄存器执行一次加载,当clang执行两次加载时,您在这里测量吞吐量,因此依赖关系图的深度没有普通计数重要。顺便说一句,像这样的寄存器之间的mov在现代CPU上通常没有延迟。movsxmm1,xmm0
合并到xmm0
。使用movaps
是复制包含任何FP值的xmm寄存器的最佳选择,即使只有低位元素有用。删除所有asm()后,依靠GCC和Clang生成自己的代码,并使用GCC和-O3链接这些对象文件,它们的吞吐量基本相同。将使用此更新问题。
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
.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