【问题标题】:Operator overloading associativity compiler optimization运算符重载关联性编译器优化
【发布时间】:2018-03-30 23:53:31
【问题描述】:

我不熟悉 C++ 中如何定义加法和乘法运算符的细节,也不知道编译器可以使用或不使用哪些信息来优化低效的操作顺序。

假设我们有一个矩阵数据类型,它实现了按元素乘以标量(即按标量缩放矩阵)。如果我们有如下代码sn-p:

Matrix a;
float b = /* value set at runtime */;
float c = /* value set at runtime */;
a = a * b * c;

如果我们天真地从左到右评估此表达式,我们将首先将a 缩放b,然后将其缩放c,而不是将a 缩放为b 和@987654328 的乘积@。编译器是否能够选择后者 (更有效的)行为而不是前者?

【问题讨论】:

  • 编译器一般不理解用户定义函数的语义。需要一个自动定理证明器来证明将表达式重写为a * c * b 不会改变其含义;编译器不太可能实现它。
  • 我记得听过一个关于某种实验性编译器的故事,它能够将int s = 0; for (int i = 0; i < n; ++i) s += i; 重写为s = n*(n+1)/2; 我强烈怀疑这是一个都市传说。
  • 如果 C++ 将乘法和加法定义为必须是关联的操作,那么我认为编译器将表达式重写为 a * (b * c)c * a * b 是公平的游戏。当然,编译器必须能够确定这些排序之一更有效。同样,我对编译器优化了解不多,所以这只是猜测。
  • 嗯,我刚刚注意到 stackoverflow.com/questions/6430448/… 。仍然不知道 -fassociative-math 是否有帮助
  • C++ 对 内置 运算符做出了某些假设。用户定义的重载运算符只是名字很有趣的函数;不对他们的行为做出任何假设。 a * b * c 只是operator*(operator*(a, b), c) 的简写;我假设您的程序定义了 operator*(float, Matrix)operator*(Matrix, float) 以及 class Matrix

标签: c++ operator-overloading operators compiler-optimization associativity


【解决方案1】:

对 Godbolt 的一些实验似乎发现,最新版本的 GCC 和 Clang 为 a * b * c 生成的代码与为 (a * b) * c 生成的代码相同,这比它为 a * (b * c) 生成的代码要长,因此似乎没有,编译器不够聪明,无法执行您所询问的优化。

您可以自己将b * c 括起来。一个更复杂的解决方案是让* 运算符生成一个表达式模板,该模板将在评估之前进行优化,因此即使用户键入(a * b) * c,您的库也会在评估之前将其重新排列为a * (b * c)

【讨论】:

  • 并不是说他们“不够聪明”。更像他们不是那么愚蠢。这会导致不同的行为并违反 C++ 规范,因为浮点数通常不是精确的表示,乘法不是关联的。即使abc 都是不涉及矩阵的浮点数。
  • @paulpro 大多数编译器都有标志允许浮点乘法的关联性,即使它改变了结果。
猜你喜欢
  • 1970-01-01
  • 2012-10-05
  • 2021-05-05
  • 2019-09-07
  • 1970-01-01
  • 1970-01-01
  • 2013-05-17
  • 1970-01-01
  • 2020-01-09
相关资源
最近更新 更多