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

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

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

#include  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) + ... 

所有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) 

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

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

提前感谢您的意见!

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

 pow(x, 2) ( = exp(2 * log(x)) ) 

 x * x 

我在这里声明的是非常依赖于编译器。 一方面,一些编译器可能甚至不知道pow(x, 2)将给出给定x的相同值(毕竟,extern函数pow可能有副作用),所以你没有任何保证共同的子表达式将被淘汰。 某些(很多?)平台/工具链上的pow函数由编译器无法控制的库提供。

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

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

 double x2 = x * x; double x3 = x * x2; double x4 = x2 * x2; 

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

编译器可以执行公共子表达式消除 – 记住它不能保证所有函数都是可重入的,但如果内联pow,那么它可能会这样做。

计算多项式的一个好方法是霍纳的规则。 (例如这里 )不需要pow()或任何额外的内存。 你的表达式是x * y乘以y中的多项式,其中每个系数的系数都是x中的多项式。

这些系数中的每一个都可以使用Horner进行计算,其中8次乘法和加法,y中的多项式有8次乘法和加法,共计74次乘法和72次加法,而您的示例代码看起来更像200次乘法和更多超过一百个电话给pow()。

根据工具链,可以优化掉电源。 你可以告诉的唯一方法就是试试看。

在一般情况下,除非编译器以宏或内联方式看到pow的实现,否则编译器无法缓存结果,因为它不知道函数可能具有哪些副作用。

简介,找出瓶颈所在。

如果频繁使用子表达式,则缓存或存储中间值可能是有意义的。 但是,访问这些值可能比将值放在处理器内的数据管道中花费更多时间。 处理器外部的数据提取比从其内部数据高速缓存提取要慢得多。

还可以尝试使用代数来简化数学表达式。 也许甚至线性代数找到一些更有效的矩阵表达式。

您可能希望将计算与包含一个变量的表达式隔离开来。 当只使用一个变量或一次更改时,编译器可以更好地优化代码。 例如,如果可能,用包含x表达式替换y变量。 这将减少到仅涉及x的表达式。

还可以在网上搜索“数据驱动设计”或“面向数据的设计”。 这些站点展示了如何优化以数据为中心的应用程序的代码。