为什么GCC不会优化a * a * a * a * a到(a * a * a)*(a * a * a)?
我正在做一些科学应用的数值优化。 我注意到的一件事是,GCC将通过编译为a*a
来优化调用pow(a,2)
,但调用pow(a,6)
未优化,并且实际上会调用库函数pow
,这会大大减慢表现。 (相比之下,英特尔C ++编译器,可执行icc
,将消除pow(a,6)
的库调用pow(a,6)
。)
我很好奇的是,当我使用GCC 4.5.1和选项“ -O3 -lm -funroll-loops -msse4
”替换pow(a,6)
使用a*a*a*a*a*a
,它使用5 mulsd
说明:
movapd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
而如果我写(a*a*a)*(a*a*a)
,它会产生
movapd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm13, %xmm13
这将乘法指令的数量减少到3. icc
具有相似的行为。
为什么编译器不能识别这种优化技巧?
因为浮点数学不关联。 用浮点乘法对操作数进行分组的方式会影响答案的数字准确性。
因此,大多数编译器对重新计算浮点计算都非常保守,除非他们确信答案会保持不变,或者除非您告诉他们您不关心数值精度。 例如:gcc的-fassociative-math
选项允许gcc重新关联浮点运算,或者甚至是-ffast-math
选项,这使得对速度的准确度进行更积极的折中。
Lambdageek正确地指出,因为关联性不适用于浮点数,所以a*a*a*a*a*a
到(a*a*a)*(a*a*a)
的“优化”可能会改变价值。 这就是C99不允许的原因(除非用户特别允许,通过编译器标志或编译指示)。 一般来说,这个假设是程序员为了某个原因编写了她所做的,编译器应该尊重它。 如果你想(a*a*a)*(a*a*a)
,那就写下来。
虽然这可能是一种痛苦, 为什么当你使用pow(a,6)
时编译器不能做[你认为是正确的]? 因为这是错误的做法。 在具有良好数学库的平台上, pow(a,6)
比a*a*a*a*a*a
或(a*a*a)*(a*a*a)
更准确。 为了提供一些数据,我在我的Mac Pro上跑了一个小实验,测量[1,2]之间的所有单精度浮点数的^ 6中的最差错误:
worst relative error using powf(a, 6.f): 5.96e-08
worst relative error using (a*a*a)*(a*a*a): 2.94e-07
worst relative error using a*a*a*a*a*a: 2.58e-07
使用pow
代替乘法树减少了误差范围的-ffast-math
。编译器不应该(通常不会)增加错误,除非用户许可这样做(例如通过-ffast-math
)来增加错误。
请注意,GCC提供了__builtin_powi(x,n)
作为pow( )
的替代方法,它应该生成一个内联乘法树。 如果你想为了性能而牺牲准确性,但不想启用快速数学,那就使用它。
另一个类似的情况:大多数编译器不会将a + b + c + d
优化为(a + b) + (c + d)
(这是一个优化,因为第二个表达式可以更好地流水线化)并将其评估为作为(((a + b) + c) + d)
)。 这也是因为角落案件:
float a = 1e35, b = 1e-5, c = -1e35, d = 1e-5;
printf("%e %en", a + b + c + d, (a + b) + (c + d));
这输出1.000000e-05 0.000000e+00
上一篇: Why doesn't GCC optimize a*a*a*a*a*a to (a*a*a)*(a*a*a)?