【问题标题】:expanding multiple "if"s with same condition result in performance gain扩展具有相同条件的多个“if”会导致性能提升
【发布时间】:2017-04-12 12:56:45
【问题描述】:

假设我有

void f(const bool condition) {
  if (condition) {
    f2();
  else {
    f3();
  }

  f4();

  if (condition) {
    f5();
  } else {
    f6();
  }
}

由于condition永远不会改变,上面可以简化为下面

void f(const bool condition) {
  if (condition) {
    f2();
    f4();
    f5();
  } else {
    f3();
    f4();
    f5();
  }
}

请注意,f4() 在第二个代码中重复,但第二个代码部分的 if 分支较少。我试图分析 2 个代码 sn-ps 但在我看来性能几乎相同。想象一下,在现实生活中,上面的代码 sn-ps 在相同条件下可以有更多if。所以我想知道现代 x86/64 处理器:

  1. 拥有 2 个巨大的 if 语句而不是许多基于相同条件的小语句是否有任何性能提升?
  2. const 关键字会帮助编译器/处理器生成更好的分支预测吗?

【问题讨论】:

  • 恕我直言,第一个看起来更好,如果您需要更改f4(),您只需执行一次而不是两次,这可能会导致错误。我首先编程是为了可读性和可维护性。在分析之后,我确定代码是一个瓶颈,那么我是否会考虑进行此类优化。
  • “const 关键字会帮助编译器/处理器生成更好的分支预测吗?”可能是最好的答案,这取决于你的编译器。
  • 在具有多 GHz 处理器的台式计算机上,单个 if 语句需要极少的时间。并且优化编译器也可能会看到条件没有改变,并为您进行转换。将时间花在其他地方可能会更好,前提是程序运行速度不够快。
  • 真正的问题是 - 在你所处的阶段你真的需要它吗? 可能不需要,那么谁在乎呢?
  • 你的意思是 f6 在第二个 sn-p 的 else 块中吗?

标签: c++ optimization branch-prediction


【解决方案1】:

首先,您的示例非常简单,任何体面的编译器都可以为这两种情况生成identical code

为了混淆它,你应该做一些更复杂的事情,而不是简单地调用f4like so

void f_seprate_ifs(const bool condition) {
  if (condition) {
    f2();
  } else {
    f3();
  }

  for ( int i = 0; i < 100; i++ ){
    f4();
  }

  if (condition) {
    f5();
  } else {
    f6();
  }
}

void f_duplicate_f4(const bool condition) {
  if (condition) {
    f2();
    for ( int i = 0; i < 100; i++ ){
      f4();
    }
    f5();
  } else {
    f3();
    for ( int i = 0; i < 100; i++ ){
      f4();
    } 
    f6();
  }
}

但这不是风格问题,而是明确的速度和空间之间的权衡 - 您正在复制代码以消除分支(和 IMO,这不是一个好的完全权衡我的例子)。编译器已经通过函数内联一直在这样做,并且在何时内联方面具有非常复杂的启发式方法。对于您的示例,它甚至为您做到了。

总而言之,除非您绝对确定它们是必要的,否则不要尝试进行此类微优化。特别是当它们损害可读性时。尤其是当它们引起复制粘贴错误时。

至于const 修饰符,任何体面的编译器都会注意到condition 永远不会改变,并且是effectively const,用Java 术语来说。在 C++ 中,const 很少提供额外的优化机会。它是给程序员的,不是给编译器的。

例如,对于:

void f(const bool& condition){

conditionNOT常量 - 编译器必须假定它可以被f4 更改,因此 sn-ps 在语义上不再等效。

【讨论】:

    【解决方案2】:

    首先,要注意任何区别,您需要多次运行 sn-ps,例如:

    for (int i=0; i<100000000; ++i)
        f(true);
    

    您需要选择迭代次数以使整体运行时间为 10-30 秒。在这种情况下,您将看到函数本身的性能,而不是加载应用程序等各种开销。

    其次,你的函数f2 ...f6的复杂度是多少?如果这些函数比f 本身复杂得多,那么您也不会注意到任何区别。

    第三,您的第二个版本会稍微快一些,尽管差异会很小。添加const 对编译器没有任何帮助。

    最后,我建议您查看能够显着提高性能的更改。

    【讨论】:

      【解决方案3】:

      理论上消除任何条件操作可以提高性能。但在现实世界中,根本没有区别。 在您的特定情况下,编译器可以轻松地为您进行建议的优化,因此应该没有区别,因为您已经测试过。优化编译器的一项有价值的工作 - 分支消除。他们寻找避免不必要分支的可能性。

      所以,你的问题 1 的答案: 在大多数情况下,在现代编译器上不会有任何区别。

      关于const关键字: const 本身对分支预测没有帮助。编译器可以查看是否有任何变量没有被修改,并尽可能地应用它们来生成快速代码。当二进制代码由处理器执行时,没有任何迹象表明该值是恒定的。至少在 x86 和 x86-64 处理器上。

      无论如何,“过早的优化是万恶之源” (c) Donald Knuth。除非您的分析数据显示瓶颈,否则您需要避免任何低级优化。为此,您需要一个基准来分析性能。

      【讨论】:

        猜你喜欢
        • 2011-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-02
        • 1970-01-01
        • 1970-01-01
        • 2019-08-06
        • 1970-01-01
        相关资源
        最近更新 更多