C++ 计算中常用值的预定义-它会改变什么吗?

C++ 计算中常用值的预定义-它会改变什么吗?,c++,c,optimization,numeric,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代码来计算大型表达式,并尝试用简单的例子来说明在单独的变量中预定义某些子部分是否有意义

作为一个简单的例子,假设我们计算以下形式的东西:

#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)
                  + ...
然而,我认为这是不必要的,因为编译器应该注意这些优化

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


提前感谢您的输入

使用带整数参数的
pow
。。。哎哟
pow
的典型实现针对浮点参数的一般情况进行了调优,这就是为什么它通常写得慢得多的原因

pow(x, 2) ( = exp(2 * log(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
可能会根据工具链进行优化。你能辨别的唯一方法就是试试看


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

概要文件,找出瓶颈所在

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

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

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


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

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


这些系数中的每一个都可以使用Horner进行8次乘法和加法计算,y中的多项式进行8次乘法和加法计算,总共74次乘法和72次加法,而在我看来,您的示例代码需要200多次乘法和100多次调用pow().

可能比读取汇编要容易得多:以每种方式运行代码(一百万次),看看执行时间是否有差异。编译器在消除常见子表达式方面确实做得很好。我同意Jefromi的观点,但请注意您使用的编译器选项。我见过一些优化(例如重写代码以使用SSE),这些优化在调试构建中稍微慢一点,但在优化的非调试构建中要快得多。编译器应该注意这一点,但是如果你有一个非常差的,它可能不会,如果你编译一个调试版本,即使是一个好的也可能不会。另一方面,对于大于2的整数指数,一个好的
pow
数学库实现将比重复乘法更精确(也就是说,并非所有的数学库都是“好的”):一个好的
pow
对于所有指数都有一个常数(亚ulp)误差界,而重复乘法的误差与指数的对数成正比。感谢您提供详细的答案,特别是关于pow()的信息!感谢指针,但实际代码看起来与简单多项式大不相同,这只是为了说明问题。
x * x
double x2 = x * x;
double x3 = x * x2;
double x4 = x2 * x2;