【问题标题】:Does a boolean condition in a for loop that is always false get optimized away?for 循环中总是为假的布尔条件是否被优化掉了?
【发布时间】:2010-12-20 20:29:57
【问题描述】:

我有以下情况

bool user_set_flag;

getFlagFromUser(&user_set_flag);

while(1){

    if(user_set_flag){
        //do some computation and output

    }


    //do other computation
}

变量user_set_flag 在代码中只设置一次,也只设置一次,一开始,它本质上是用户选择他想对程序做什么。假设用户选择了user_set_flag = false,那么编译器将编译代码以使if(user_set_flag) 语句只被检查一次,或者总是被检查。我可以给编译器提示,比如将 bool 设置为 const 吗?

我问这个的原因是因为我的应用程序是时间关键的,它处理帧尽可能快。始终为假的分支应该能够在运行时以某种方式确定?

【问题讨论】:

  • 我相信除了单个 if 语句之外,您还可以找到其他需要优化的东西。
  • 您的问题的答案取决于编译器。编译器不需要对其进行优化。但如果它是一个可以在编译时确定的常量,大多数编译器都会将其优化掉。
  • 编译器无法优化掉用户提供的值。那需要一台时光机。

标签: c++ optimization loops


【解决方案1】:

首先,处理器具有称为 branch prediction 的功能。在循环运行几次之后,处理器将能够注意到您的 if 语句总是以一种方式运行。 (它甚至可以注意到常规模式,例如 true false true false。)然后它会 speculatively execute 那个分支,只要它能够正确预测,if 语句的额外成本就是几乎消除了。如果您认为用户更有可能选择true 而不是false,您甚至可以选择tell this to the gcc compiler(gcc 特定扩展)。

但是,您确实在您的一个 cmets 中提到您有一个“更复杂的布尔序列”。我认为处理器可能没有内存来模式匹配所有这些跳转——当它回到第一个if 语句时,关于跳转的方向的知识已经从它的记忆。但我们可以在这里提供帮助...

编译器能够将循环和 if 语句转换成它认为更优化的形式。例如。它可能会将您的代码转换为 schnaader 给出的形式。这被称为loop unswitching。您可以通过 Profile-Guided Optimization (PGO) 来帮助它,让编译器知道热点在哪里。 (注意:在 gcc 中,-funswitch-loops 仅在 -O3 处开启。)

您应该在指令级别分析您的代码(VTune 将是一个很好的工具)以查看 if 语句是否真的是瓶颈。如果确实如此,并且通过查看生成的程序集,您认为编译器尽管 PGO 还是出错了,您可以尝试自己提升 if 语句。也许模板化的代码会更方便:

template<bool B> void innerLoop() {
    for (int i=0; i<10000; i++) {
        if (B) {
            // some stuff..
        } else {
            // some other stuff..
        }
    }
}
if (user_set_flag) innerLoop<true>();
else innerLoop<false>();

【讨论】:

  • 关于分支预测的好点,人们总是忘记这一点,但它对这些事情的影响非常大。
  • 如果user_set_flag 可以确定为常量,我仍然希望编译器在这里进行优化(函数本地,在循环调用之前没有传递任何句柄并且在例如循环),那么我希望编译器能够生成两个版本的循环,如果它可以显着加速......(即如果减少指令数量是值得的)。
  • 是的,我说过它可以..但如果它没有,我们可以通过使其更明确来鼓励它。
  • 这一切都取决于some stuffsome other stuff 实际上是什么。他们必须几乎什么都不是,这才重要。
【解决方案2】:

我认为根本不可能进一步优化它。编译器足够聪明,知道user_set_flag 的值在循环执行期间不会改变,并且会为此生成最有效的机器代码。

这在某种程度上也属于对编译器的猜测。除非你真的真的很清楚自己在做什么,否则最好坚持使用最简单的解决方案。

作为练习,尝试使用if (true)if(user_set_flag) 来分析(时间)执行。我的猜测是执行时间的差异为零。

【讨论】:

    【解决方案3】:

    另一种选择是:

    if(user_set_flag){
        while(1){
          ComputationAndOutput();
          OtherComputation();
        }
    } else {
        while(1){
          OtherComputation();
        }
    }
    

    但正如 Smashery 已经说过的,这是微优化,不会像其他优化那样加速您的程序。

    【讨论】:

    • 是的,我正在考虑这样的事情 - 但我也想知道调用函数的额外努力是否不值得
    • 好吧,在这种情况下,您可以复制代码而不是使用函数 - 但再一次,所有这些只会让您的代码更快一点,并不值得。
    • 例如,调用函数或评估 if 可能是 1-3 个 CPU 周期,我确信您在循环中执行的剩余代码将浪费数千个 CPU 周期,因此通过此类优化加速将低于 1%。
    • @chnaader:如果你有一个更复杂的布尔序列,只需要评估一次并且对运行时间造成重大损失怎么办?
    • 那么优化可能是有用的,但我怀疑这种情况是否很常见(如果是,那么您的编码技术中存在错误,这不是优化的情况,而是编写好的代码) .
    【解决方案4】:

    从技术上讲,编译器可以优化这样的情况。

    例如:

    #include <cstdio>
    
    int main(int argc, char* [])
    {
        while (true)
        {
            if (argc == 1) {
                puts("one");
            }
            puts("some more");
        }
    }
    

    main 编译为 (G++ -O3):

        cmpl    $1, 8(%ebp)
        je  L9
        .p2align 4,,15
    L2:
        movl    $LC1, (%esp)
        call    _puts
        jmp L2
    L9:
        movl    $LC0, (%esp)
        call    _puts
        movl    $LC1, (%esp)
        call    _puts
        movl    $LC0, (%esp)
        call    _puts
        movl    $LC1, (%esp)
        call    _puts
        jmp L9
    

    如您所见,该条件仅评估一次以确定运行哪个循环。它已经展开了真正的分支:)

    我会得出结论,没有理由担心这些微优化,除非您确定编译器无法优化对不变布尔值的重复评估(例如,如果它是全局的,编译器如何知道它不会被函数调用修改),它确实是瓶颈。

    【讨论】:

    • ++ 我总是支持新手。你是对的,没有必要担心这些。每当优化循环时,请查看循环的内容。在这种情况下,_puts 将消耗比循环开销更多数量级的时间,因此循环可能完全未优化,您永远不会注意到。
    【解决方案5】:

    您说用户实际上有一个设置可以将此标志设置为truefalse。这意味着它可以在运行时改变。这意味着,它不能被优化掉(通常)。

    一般来说,编译器只能“优化掉”它在编译时知道的东西。这意味着:此时您点击编辑器菜单中的“构建”项。如果它可以改变,它 - 通常 - 不能被优化掉。

    但是,自己优化它相当容易(嗯,取决于您未显示的部分)。如果您对循环内使用的一条汇编指令感到困扰,请将 if 语句放在循环外。这样一来,函数的调用只执行一次。

    【讨论】:

      【解决方案6】:

      如果你在编译时知道flag的值,你可以添加编译标志不包括if语句为:

      while(1){
         #ifdef user_set_flag
          {
              //do some computation and output
      
          }
         #endif
      
      
          //do other computation
      }
      

      【讨论】:

        【解决方案7】:

        如果您真的想要尽可能快,那么您想要进行积极的性能调整。所以别再去猜测编译器可能会做什么来优化你的程序了。

        这是胆小。

        相反,负责。 This shows you how.

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-19
          • 1970-01-01
          • 1970-01-01
          • 2014-03-05
          相关资源
          最近更新 更多