为什么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

链接地址: http://www.djcxy.com/p/277.html

上一篇: Why doesn't GCC optimize a*a*a*a*a*a to (a*a*a)*(a*a*a)?

下一篇: Why does C++ compilation take so long?