我正在对一个科学应用程序进行数值优化。我注意到的一件事是,GCC将通过将调用pow(a,2)编译为a*a来优化它,但调用pov(a,6)并没有优化,实际上会调用库函数pow,这会大大降低性能。(相比之下,可执行icc的“英特尔C++编译器”将消除对pow(a,6)的库调用。)
我好奇的是,当我使用GCC 4.5.1和选项“-O3-lm-funroll-loops-msse4”将pow(a,6)替换为a*a*a*a*a*a时,它使用了5条多指令:
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也有类似的行为。
为什么编译器不认识这种优化技巧?
Fortran(为科学计算而设计)有一个内置的幂运算符,据我所知,Fortran编译器通常会以与您描述的方式类似的方式优化整数幂的提升。不幸的是,C/C++没有幂运算符,只有库函数pow()。这并不妨碍智能编译器专门处理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);
}
为好奇的人澄清:这并没有找到计算幂的最佳方法,但由于找到最佳解是一个NP完全问题,而且这只值得对小幂做(而不是使用pow),因此没有理由大惊小怪。
然后将其用作功率<6>(a)。
这样可以很容易地输入幂(不需要像用括号一样拼出6),并且可以在不使用数学的情况下进行这种优化,以防出现精度相关的情况,例如补偿求和(这是一个操作顺序至关重要的示例)。
您可能也会忘记这是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
对于功率<6>(a),
mulsd %xmm0, %xmm0
movapd %xmm0, %xmm1
mulsd %xmm0, %xmm1
mulsd %xmm0, %xmm1