在计算中预先定义常用值 - 它会改变什么吗?

bbt*_*trb 1 c c++ optimization numeric

我正在自动生成C代码来计算大型表达式,并试图用简单的例子弄清楚在单独的变量中预定义某些子部分是否有意义.

举个简单的例子,假设我们计算了一些形式:

#include <cmath>
double test(double x, double y) {
    const double c[9][9] = { ... }; // constants properly initialized, irrelevant
    double expr =   c[0][0]*x*y 
                  + c[1][0]*pow(x,2)*y        + ... + c[8][0]*pow(x,9)*y 
                  + c[1][1]*pow(x,2)*pow(y,2) + ... + c[8][1]*pow(x,9)*pow(y,2)
                  + ...
Run Code Online (Sandbox Code Playgroud)

所有c [i] [j]正确初始化.实际上,这些表达式包含数以千万计的乘法和加法.

现在提出一个同事 - 减少对pow()的调用次数并缓存表达式中经常需要的值 - 在单独的变量中定义x和y的每个幂,这没什么大不了的,因为代码是自动的无论如何生成,像这样:

double xp2 = pow(x,2);
double xp3 = pow(x,3);
double xp4 = pow(x,4);
// ...
// same for pow(y,n)
Run Code Online (Sandbox Code Playgroud)

但是,我认为这是不必要的,因为编译器应该处理这些优化.

不幸的是,我没有阅读和解释汇编的经验,但我想我看到所有对pow()的调用都被优化了,这是对的吗?此外,编译器是否缓存pow(x,2),pow(x,3)等的值?

提前感谢您的意见!

Ale*_* C. 8

使用pow整数参数...哎哟!pow针对浮点参数的一般情况调整了典型的实现,这就是为什么写入通常会慢一些的原因

pow(x, 2) ( = exp(2 * log(x)) )
Run Code Online (Sandbox Code Playgroud)

x * x
Run Code Online (Sandbox Code Playgroud)

我在这里声明的是非常依赖于编译器.一方面,一些编译器可能甚至不知道pow(x, 2)将给出给定的相同值x(毕竟,extern函数pow可能有副作用),因此您无法保证将消除公共子表达式.的pow功能,在某些(许多?)平台/工具链,由一个库提供的编译器没有控制到.

但是,在其他实现中,编译器可能会将这些pow调用转换为乘法,或者至少转换为内部函数,而内部函数又可以专门用于整数指数.你的里程有所不同.

我要做的第一件事就是pow通过乘法来替换调用.对于较大的指数,您也可以这样做,例如.

double x2 = x * x;
double x3 = x * x2;
double x4 = x2 * x2;
Run Code Online (Sandbox Code Playgroud)

注意(对@Stephen Canon的信用)进行重复乘法(使用上述快速取幂方案)将引入舍入误差,其幅度与乘法的数量成比例(即O(对数指数)).此错误通常是可以容忍的,但pow保证在一个最小精度单位内的准确性.

  • 另一方面,对于大于2的整数指数,一个好的数学库实现`pow`将比重复乘法更准确(也就是说,并非所有的数学库都是"好的"):一个好的`pow`有一个常数(sub-ulp)所有指数的误差界限,而重复乘法的误差与指数的对数成正比. (2认同)