optimization - 为什么GCC不优化a*a*a*a*a*a ( a*a*a ) *( a*a*a )?

  显示原文与译文双语对照的内容
0 0

我在科学应用程序上进行了一些数值优化。 我注意一件事情是,GCC会优化电话 pow(a,2) 通过编译它输入调用 pow(a,6)a*a,但未做优化,实际上将调用库 pow 作用,这极大地减慢性能。 ( 相反,C++ 编译器 。exe可以执行的icc 将消除 pow(a,6) 调用的库调用。)

使用研磨碳酸钙 4.5.1和选项",什么我很好奇的是,当我 pow(a,6) 换成 a*a*a*a*a*a-O3 -lm -funroll-loops -msse4 ",它使用 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 具有相似的行为。

编译器为何无法识别这里优化技巧?

时间: 原作者:

0 0

因为浮点数学不是关联的 。 在浮点乘法中分组操作数的方式对答案的数值精度有影响。

因此,大多数编译器都非常保守地重新排序浮点计算,除非它们能够确保答案保持不变,否则除非你告诉他们你不关心数字精度。 例如:-fassociative-math 选项,它允许gcc重新关联浮点操作,甚至是 -ffast-math 选项,它允许更积极地权衡精度和速度。

原作者:
0 0

Lambdageek 正确指出,由于关联性不保留floating-point数字,a*a*a*a*a*a(a*a*a)*(a*a*a)的"优化"可能会更改值。 这就是( 除非用户特别允许,否则通过编译器标志或者杂注) 不允许它的原因。 通常,假设是程序员写了她所做的事情,编译器应该尊重。 如果你想要 (a*a*a)*(a*a*a),请写。

,会有麻烦来写,不过当你使用 pow(a,6) [what you consider to be] ;为什么不能编译器只做正确的事情? 因为它将是该错误来说,这样做。 在一个具有良好数学库的平台上,pow(a,6)a*a*a*a*a*a 或者 (a*a*a)*(a*a*a) 更精确。 只是为了提供一些数据,我做了一次小实验我使用的是 MacPro [ 之间,为把single-precision浮动测量中最糟糕的错误评估a^6数字1,2 ):


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 代替一次乘法树通过一个因素 4 。 编译器不应该( 而且通常并不) 生成增加错误的"优化",除非用户授权这样做( 例如。 通过 -ffast-math ) 。

注意,GCC提供 __builtin_powi(x,n) 作为 pow( )的替代,它应该生成一个内联乘法树。 如果你想权衡性能,但不想启用 fast-math,请使用。

原作者:
0 0

另一种类似情况:大多数编译器不会优化 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));

0 0

Fortran ( 为科学计算而设计) 有一个内置的power操作符,据我所知,Fortran编译器将通常以类似于你描述的方式将提升到整数幂。 C/C++ 没有电源操作符,只有库函数 pow() 。 这并不防止智能编译器专门处理 pow,并以更快的速度计算它,但似乎它们不太常见。

几年前,我试图以一种最佳的方式来计算整数幂,并得到了如下结果。 它是 C++,而不是C,它仍然依赖于编译器对如何优化/内联事物的聪明。 总之,希望你在实践中发现它有用:


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);
}

于该curious,相关 clarification: 随着 detail, 这不会找到最佳的方式来计算幂,但是由于获取最优解决方案是一个 NP-complete问题而且这仅仅是值得做为小功率不过( 与使用 pow 相反) 啦没有理由 fuss.

然后使用它作为 power<6>(a)

这可以很容易地键入( 与无需拼写出 6 a s ) 开机时,并让你有这种类型的优化,而 -ffast-math 以防你有一些精密相关,例如补偿总和 ( 操作顺序是必需的示例) 。

你可能也会忘记这是 C++,只是在C 程序( 如果使用 C++ 编译器编译) 中使用它。

希望这是有用的。

编辑:

这就是我从编译器中得到的:

对于 a*a*a*a*a*a


 movapd %xmm1, %xmm0
 mulsd %xmm1, %xmm0
 mulsd %xmm1, %xmm0
 mulsd %xmm1, %xmm0
 mulsd %xmm1, %xmm0
 mulsd %xmm1, %xmm0

对于 (a*a*a)*(a*a*a)


 movapd %xmm1, %xmm0
 mulsd %xmm1, %xmm0
 mulsd %xmm1, %xmm0
 mulsd %xmm0, %xmm0

对于 power<6>(a)


 mulsd %xmm0, %xmm0
 movapd %xmm0, %xmm1
 mulsd %xmm0, %xmm1
 mulsd %xmm0, %xmm1

原作者:
0 0

当一个整数是整数时,GCC实际上会将a*a*a*a*a*a优化为( a*a*a ) * ( a*a*a ) 。 我尝试了以下命令:


$ echo 'int f(int x) { return x*x*x*x*x*x; }' | gcc -o - -O2 -S -masm=intel -x c -

有很多gcc标志,但没有什么奇特的。 在C 语言( 通常从输入文件扩展推断出语言,但在读取标准文件时没有文件扩展名) ;并写入stdout,它们的意思:从stdin中读取时;使用氧气优化级别;输出汇编语言清单,而不是一个二进制的输入包是;清单应当使用英特尔汇编语言语法;

下面是输出的重要部分。 在language,我有带注释的程序集,它通过一些注释,指示发生了什么 on:


 ; x is in edi to begin with. eax will be used as a temporary register.
 mov eax, edi ; temp1 = x
 imul eax, edi ; temp2 = x * temp1
 imul eax, edi ; temp3 = x * temp2
 imul eax, eax ; temp4 = temp3 * temp3

我在 Linux Mint 16 Petra上使用系统 GCC,这是一个Ubuntu衍生物。 这是gcc的版本:


$ gcc --version
gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1

就像其他海报有提到的浮点数中,这里选项是不可能的,因为浮点运算实际上是没有关联的。

...