【问题标题】:Does gcc optimize c++ code algebraically and if so to what extent?gcc 是否以代数方式优化 c++ 代码,如果是,优化到什么程度?
【发布时间】:2016-01-19 14:56:55
【问题描述】:

考虑以下显示一些简单算术运算的代码

    int result = 0;

    result = c * (a + b) + d * (a + b) + e;

要在上面的表达式中得到结果,cpu 需要执行两个整数乘法和三个整数加法。然而,在代数上,上面的表达式可以简化为下面的代码。

 result = (c + d) * (a + b) + e

这两个表达式在代数上是相同的,但是第二个表达式只包含一个乘法和三个加法。 gcc(或其他编译器)是否能够自行进行这种简单的优化。

现在假设编译器足够智能,可以进行这种简单的优化,它是否能够优化更复杂的东西,例如Trapezoidal rule(用于数值积分)。下面的示例近似于sin(x) 下的区域,其中0 <= x <= pi 的步长为 pi/4(为了简单起见很小)。请假设所有文字都是运行时变量。

#include <math.h>

// Please assume all literals are runtime variables. Have done it this way to 
// simplify the code.
double integral = 0.5 * ((sin(0) + sin(M_PI/4) * (M_PI/4 - 0) + (sin(M_PI/4) + 
    sin(M_PI/2)) * (M_PI/2 - M_PI/4) + (sin(M_PI/2) + sin(3 * M_PI/4)) * 
    (3 * M_PI/4 - M_PI/2) + (sin(3 * M_PI/4) + sin(M_PI)) * (M_PI - 3 * M_PI/4));

现在上面的函数可以用梯形规则简化后写成这样。这大大减少了获得相同答案所需的乘法/除法次数。

integral = 0.5 * (1 / no_steps /* 4 in th case above*/) * 
    (M_PI - 0 /* Upper and lower limit*/) * (sin(0) + 2 * (sin(M_PI/4) +
    sin(3 * M_PI/4)) + sin(M_PI));

【问题讨论】:

  • 请提供 C/C++ 语言规范的链接。在此之前,只有两种不同语言 C 和 C++。选一个!而且您的问题对于SO来说太宽泛了。请自己做一些研究。 gcc 邮件列表可能是一个好的开始。或者你只是阅读源代码。
  • 您可以随时编译并检查程序集以查看它的作用。
  • 当心!当您开始考虑整数溢出时,这种代数重排通常会中断。我想不出这个特定表达式的反例,但是有很多表达式会破坏它。
  • 您的第二个示例(带有浮点)不太可能被优化。浮点运算遵守 (a-b)*c == a*c - b*c 这样的代数恒等式 - 下溢会导致各种奇怪的行为。

标签: c++ c gcc g++


【解决方案1】:

GCC(以及大多数 C++ 编译器,就此而言)不会重构代数表达式。

这主要是因为就 GCC 和一般软件算法而言,这些行

double x = 0.5 * (4.6 + 6.7);
double y = 0.5 * 4.6 + 0.5 * 6.7;

assert(x == y); //Will probably fail!

不保证被评估为完全相同的数字。如果没有这种保证,GCC 无法优化这些结构。

此外,操作顺序也很重要。例如:

int x = y;
int z = (y / 16) * 16;

assert(x == z); //Will only be true if y is a whole multiple of 16

在代数上,这两行应该是等价的,对吧?但是如果yint,它实际上会做的是使x 等于“y 四舍五入到16 的下整数倍”。有时,这是预期的行为(例如,如果您在进行字节对齐)。其他时候,这是一个错误。重要的是,两者都是有效的计算机代码,并且都可以根据情况发挥作用,如果 GCC 围绕这些结构进行优化,它将阻止程序员对他们的代码拥有代理权。

【讨论】:

  • 根据 nathan Oliver 的建议,我分解了上面的整数示例,它生成了完全相同的程序集。在那种情况下,gcc 通过将result = c * (a + b) + d * (a + b) + e; 重构为result = (c + d) * (a + b) + e; 或者我不知道的其他方式进行了优化。但是,关于浮点示例,您是对的。我不懂组装,所以无法弄清楚那里发生了什么。
  • 在示例 1 中,编译器将简单地对计算结果进行硬编码,不需要运行时计算。但是,对于非常量值,它可能会在启用 -ffast-math 的情况下进行优化。 2.那个例子不正确,因为数学/与C++/不一样。此外,GCC 确实将其优化为简单的按位运算。
【解决方案2】:

是的,包括 gcc 在内的优化器会进行这种类型的优化。不一定是您准确引用的表达式,或其他任意复杂的表达式。但更简单的表达式 (a + a) - a 可能会优化为 a 例如。另一个可能优化的例子是a*a*a*atemp = a*a; (temp)*(temp)

一个给定的编译器是否优化了你引用的表达式,可以通过阅读输出的汇编代码来观察。

不,这种类型的优化默认不与浮点一起使用(除非优化器可以证明没有精度损失)。请参阅Are floating point operations in C associative?可以让例如 gcc 使用-fassociative-math 选项来执行此操作。 后果自负。

【讨论】:

  • @user202729 只有有符号整数溢出是未定义的。无符号算术是模块化的。我不记得当我写答案时我想说什么。我将删除该声明,因为它具有误导性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
  • 2013-04-27
  • 1970-01-01
  • 2017-10-27
  • 2012-04-01
相关资源
最近更新 更多