【问题标题】:Will the C/C++ compiler optimize this if statement?C/C++ 编译器会优化这个 if 语句吗?
【发布时间】:2009-03-01 20:58:53
【问题描述】:

我有这样的代码,但我觉得有点难以阅读:

// code1
if( (expensiveOperation1() && otherOperation() && foo()) 
     || (expensiveOperation2() && bar() && baz()) {
  // do something
}

我只是将其更改为以下内容,以使其更具可读性:

// code2
const bool expr1 = expensiveOperation1() && otherOperation() && foo();
const bool expr2 = expensiveOperation2() && bar() && baz();
if(expr1 || expr2){
   // one of the conditions met
}

但是我现在应该关注效率吗?

我的意思是,在code1 中,如果第一个连接子句得到满足,那么它甚至不会费心去看第二个,因为很明显该陈述将是正确的。

但在我更易读的示例中,cond1cond2 都必须计算。或者如果 expr2 没有在其他任何地方使用,编译器是否足够聪明,可以将我的 code2 更改为 code1

【问题讨论】:

  • 我正在使用 gcc/g++(准确地说是 g++ v3.4)。

标签: c++ c optimization compiler-construction performance


【解决方案1】:

我会说不应该,因为如果任何函数有副作用,它们在逻辑上就不等效。

然而,以下内容是等效的,它的优点是允许您为测试函数提供描述性名称,从而使代码更具自我记录性:

// code3
inline bool combinedOp1()
{
    return expensiveOperation1() && otherOperation() && foo();
}

inline bool combinedOp2()
{
    return expensiveOperation2() && bar() && baz();
}

然后调用如下:

if (combinedOp1() || combinedOp2())
{
    // do something
}

【讨论】:

  • 由于懒惰的评估,你最好不要有任何副作用。
  • 同意,如果它优化了这一点,我会感到惊讶。当然,不应该有任何副作用,但编译器不会知道这一点。它必须假设最坏的情况。如果您的昂贵操作的定义是可见的,它可能优化它,但这不是一个安全的选择。不过我喜欢这个解决方案。
  • 嗯,你的意思是,在某些情况下,这将执行 combineOp2(),即使 combineOp1() 已经为真?任何人都可以确认确实如此吗?因为如果是这样,那么这不应该是公认的答案。
  • 不,他不是这个意思。如果 combineOp1() 为真,则保证永远不会调用 combineOp2()。
【解决方案2】:

也许吧,但为什么不将第二张支票纳入第一张呢?

// code3
bool expr = expensiveOperation1() && otherOperation() && foo();
expr = expr || (expensiveOperation2() && bar() && baz());
if(expr){
   // one of the conditions met
}

更好的是,扭转局面,使成本最低的检查首先出现在每个列表中,利用惰性求值完全跳过成本高昂的操作。

【讨论】:

  • 很好 - 这是我在一百万年后可能不会想到的简单事情之一。
  • 很好,除了 expr 被声明为 const 所以第二行不会编译:-) 我猜你的意思是“const bool expr1 = ...; const bool expr2 = expr1 || ( ...)"
  • 剪切/粘贴错误。我可能不会将其设为 const 并重用它。
  • 一个缺点是很难找到描述性的名称。在 dehmann 的版本中,可以命名 expr1 和 expr2 变量来表达子条件。
  • @tvanfosson - dehmann 给出“code2”的原因是为了让 code1 更具可读性。毫无疑问,最好使用明确、独特的 expr1 和 expr2 名称来实现,这可能是您解决方案的一个缺点。不必如此,所以我没有反对,只是想指出这一点。
【解决方案3】:

好吧,如果条件有副作用,编译器通常不会重新排序 && 和 ||。一些非常聪明的编译器可能能够静态验证它们的独立性,但这种情况很少见。

如果可能,请重新排列条件,让廉价操作先出现,这样就可以将昂贵的操作短路。

【讨论】:

    【解决方案4】:

    这里的最佳答案是用“不应该”和“也许”来回答问题!这不是一个确定的答案来吧!

    如果您想知道您的编译器是否正在优化这一小段代码,请使用“显示汇编输出”标志编译您的代码。在 GCC 上,该标志是“-S”。然后查看输出程序集,它会 100% 准确地显示正在编译的内容。

    然后,您可以将您的第一个代码片段与“therefromhere”中的代码 sn-p 进行比较,并快速尝试大量代码更改,直到找到编译器优化得最好的代码(即最少周期)。

    查看 asm 输出听起来既复杂又可怕,但实际上只需要大约 5 分钟即可完成。我在这里做了一个例子:What is the fastest way to swap values in C?

    【讨论】:

    • -1 这是生成在下一次编译器更新时中断的不可移植代码的好方法。始终尝试了解语言所保证的内容,只有当行为似乎违反了其中一项保证时,您才应该开始分析编译器实现细节。
    • 我什么时候建议过“哦,当您查看编译器的输出 asm 时...确保您的 C 代码不可移植”。您一定非常努力地将我的文章误解为“使您的代码不可移植”。只有傻瓜才会故意使他们的 C 代码不可移植。一个更大的傻瓜会不加思索地非故意地使他们的 C 代码不可移植。
    【解决方案5】:

    这个问题的答案当然取决于编译器。确定的检查方法是查看编译器为此函数生成的程序集。大多数(全部?)编译器都有办法做到这一点,例如gcc-S 选项。如果由于某些奇怪的原因,大多数调试器无法向您展示函数的反汇编,或者还有其他工具可以做到这一点。

    【讨论】:

      【解决方案6】:

      很好的答案。

      我只想补充一点,我不喜欢编译器在优化方面过于激进,以至于重新排序我的代码。

      我只想让编译器按照它的指示去做。

      如果它能智胜我,它也能智胜自己。

      【讨论】:

        【解决方案7】:

        如果编译器知道 cond2 中的函数(expensiveOperation2()、bar() 和 baz())是纯函数(即没有副作用),则它可以进行优化。如果它们是纯的,确保编译器知道它的最简单方法是使它们成为内联函数。

        可能即使你不知道,编译器也能分辨出来,但这不太可能,因为昂贵的Operation2() 可能做了很多工作。

        FWIW,如果这些函数是纯函数,您可能应该重新排序它们,以便 bar() 和 baz() 在昂贵操作 2() 之前运行(与 cond1 中的排序相同)。

        【讨论】:

          猜你喜欢
          • 2010-12-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-30
          • 1970-01-01
          • 2017-06-16
          • 1970-01-01
          • 2013-09-11
          相关资源
          最近更新 更多