我正在尝试生成的程序集并发现了一件有趣的事情。 有两个函数执行相同的计算。它们之间的唯一区别在于结果总和的方式。
#include <cmath>
double func1(double x, double y)
{
double result1;
double result2;
if (x*x < 0.0) result1 = 0.0;
else
{
result1 = x*x+x+y;
}
if (y*y < 0.0) result2 = 0.0;
else
{
result2 = y*y+y+x;
}
return (result1 + result2) * 40.0;
}
double func2(double x, double y)
{
double result = 0.0;
if (x*x >= 0.0)
{
result += x*x+x+y;
}
if (y*y >= 0.0)
{
result += y*y+y+x;
}
return result * 40.0;
}
x86 clang 3.7生成的程序集-O2
gcc.godbolt.org {{3}}开启的程序集是如此不同和意外。 (gcc上的编译导致类似的程序集)
.LCPI0_0:
.quad 4630826316843712512 # double 40
func1(double, double): # @func1(double, double)
movapd %xmm0, %xmm2
mulsd %xmm2, %xmm2
addsd %xmm0, %xmm2
addsd %xmm1, %xmm2
movapd %xmm1, %xmm3
mulsd %xmm3, %xmm3
addsd %xmm1, %xmm3
addsd %xmm0, %xmm3
addsd %xmm3, %xmm2
mulsd .LCPI0_0(%rip), %xmm2
movapd %xmm2, %xmm0
retq
.LCPI1_0:
.quad 4630826316843712512 # double 40
func2(double, double): # @func2(double, double)
movapd %xmm0, %xmm2
movapd %xmm2, %xmm4
mulsd %xmm4, %xmm4
xorps %xmm3, %xmm3
ucomisd %xmm3, %xmm4
xorpd %xmm0, %xmm0
jb .LBB1_2
addsd %xmm2, %xmm4
addsd %xmm1, %xmm4
xorpd %xmm0, %xmm0
addsd %xmm4, %xmm0
.LBB1_2:
movapd %xmm1, %xmm4
mulsd %xmm4, %xmm4
ucomisd %xmm3, %xmm4
jb .LBB1_4
addsd %xmm1, %xmm4
addsd %xmm2, %xmm4
addsd %xmm4, %xmm0
.LBB1_4:
mulsd .LCPI1_0(%rip), %xmm0
retq
func1
编译为无分支程序集,涉及的指令少于func2
。因此func2
预计会比func1
慢得多。
有人可以解释这种行为吗?
答案 0 :(得分:15)
比较运算符<
或>=
的此行为的原因因double
是NaN
还是NaN
而异。其中一个操作数为NaN
的所有比较都返回false
。因此,无论x*x < 0.0
是否为x
,您的NaN
总是都是假的。因此编译器可以安全地优化它。但是,x * x >= 0
的情况对NaN
和非NaN
值的行为会有所不同,因此编译器会在程序集中保留条件跳转。
这是cppreference关于与所涉及的NaN进行比较的说法:
转换后操作数的值按通常的数学意义进行比较(除了正负零比较相等,任何涉及NaN值的比较返回零)