Gcc 为什么';是否将a*a*a*a*a*a优化为(a*a*a)*(a*a*a)?

Gcc 为什么';是否将a*a*a*a*a*a优化为(a*a*a)*(a*a*a)?,gcc,assembly,floating-point,compiler-optimization,fast-math,Gcc,Assembly,Floating Point,Compiler Optimization,Fast Math,我正在做一些科学应用的数值优化。我注意到的一件事是,GCC将通过将调用编译成a*a来优化调用pow(a,2),但是调用pow(a,6)没有优化,实际上会调用库函数pow,这大大降低了性能。(相反,可执行的icc,将消除对pow(a,6)的库调用) 我好奇的是,当我使用GCC 4.5.1和选项“-O3-lm-funroll loops-msse4”将pow(a,6)替换为a*a*a*a*a”时,它使用了5个mulsd指令: movapd %xmm14, %xmm13 mulsd %xmm14

我正在做一些科学应用的数值优化。我注意到的一件事是,GCC将通过将调用编译成
a*a
来优化调用
pow(a,2)
,但是调用
pow(a,6)
没有优化,实际上会调用库函数
pow
,这大大降低了性能。(相反,可执行的
icc
,将消除对
pow(a,6)
的库调用)

我好奇的是,当我使用GCC 4.5.1和选项“
-O3-lm-funroll loops-msse4
”将
pow(a,6)
替换为
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允许gcc重新关联浮点运算,或者甚至是
-ffast math
选项,该选项允许在精度和速度之间进行更积极的权衡。

正确地指出,由于关联性不适用于浮点数,
a*a*a*a
的“优化”是
(a*a*a)*(a*a*a)
可能会更改该值。这就是C99不允许更改该值的原因(除非用户通过编译器标志或pragma明确允许)。通常,假设程序员编写她所做的事情是有原因的,编译器应该尊重这一点。如果您想要
(a*a)*(a*a*a)
,请编写该代码

这可能是一个痛苦的写作,然而,为什么编译器不能只做[你认为是正确的事情,当你使用的是一个好的数学库,<代码> > PoW(a,6)< /> >比<<代码> > a*a*a*a*< /> >或<代码>(a*a*a)*(a*a*a)要精确得多。。为了提供一些数据,我在Mac Pro上做了一个小实验,测量了[1,2]之间所有单精度浮点数计算^6时的最大误差:

使用powf(a,6.f)的最差相对误差:5.96e-08
使用(a*a*a)*(a*a*a)的最差相对误差:2.94e-07
使用a*a*a*a*a*a*a的最差相对误差:2.58e-07
使用
pow
而不是乘法树可以减少因子4限制的错误。编译器不应该(通常也不会)进行增加错误的“优化”,除非用户许可(例如通过
-ffast math


请注意,GCC提供了
\uuu builtin\u 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 %e\n", a + b + c + d, (a + b) + (c + d));

这将输出
1.000000e-05 0.000000e+00

,因为32位浮点数(如1.024)不是1.024。在计算机中,1.024是一个间隔:从(1.024-e)到(1.024+e),其中“e”表示一个错误。有些人没有意识到这一点,还认为*in a*a代表任意精度数字的乘法,而这些数字没有任何错误。有些人没有意识到这一点的原因可能是他们在小学时进行的数学计算:只使用理想数rs没有附加错误,并且认为在执行乘法时忽略“e”是可以的。他们没有看到“浮点a=1.2”、“a*a*a”和类似的C代码中隐含的“e”

如果大多数程序员认识到(并且能够执行)C表达式a*a*a*a*a*a*a实际上不能与理想数一起工作,那么GCC编译器就可以自由地将“a*a*a*a*a”优化为“t=(a*a);t*t*t”但不幸的是,GCC编译器不知道编写代码的程序员是否认为“a”是一个有错误或没有错误的数字,因此GCC只会将d
template<unsigned N> struct power_impl;

template<unsigned N> struct power_impl {
    template<typename T>
    static T calc(const T &x) {
        if (N%2 == 0)
            return power_impl<N/2>::calc(x*x);
        else if (N%3 == 0)
            return power_impl<N/3>::calc(x*x*x);
        return power_impl<N-1>::calc(x)*x;
    }
};

template<> struct power_impl<0> {
    template<typename T>
    static T calc(const T &) { return 1; }
};

template<unsigned N, typename T>
inline T power(const T &x) {
    return power_impl<N>::calc(x);
}
    movapd  %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    movapd  %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm0, %xmm0
    mulsd   %xmm0, %xmm0
    movapd  %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
$ echo 'int f(int x) { return x*x*x*x*x*x; }' | gcc -o - -O2 -S -masm=intel -x c -
; x is in edi to begin with.  eax will be used as a temporary register.
mov  eax, edi  ; temp = x
imul eax, edi  ; temp = x * temp
imul eax, edi  ; temp = x * temp
imul eax, eax  ; temp = temp * temp
$ gcc --version
gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1
pow(x,y);
float a=someValue;
float b=a*a*a*a*a*a;
double foo(double a) {
  return a*a*a*a*a*a;
}
foo(double):
    mulsd   %xmm0, %xmm0
    movapd  %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    mulsd   %xmm1, %xmm0
    ret
foo(long):
    movq    %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rax, %rax
    ret