如何使每帧分支优化友好?

How to make a per-frame branch optimization-friendly?

提问人:Luchian Grigore 提问时间:5/7/2014 更新时间:5/7/2014 访问量:111

问:

假设我有一个主循环,每帧更新不同的内容:

int currentFrame = frame % n;
if ( currentFrame == 0 )
{
   someVar = frame;
}
else if ( currentFrame == 1 )
{
   someOtherVar = x;
}
...
else if ( currentFrame == n - 1 )
{
   someMethod();
}

我可以让它对分支预测器更友好吗?分支预测器能否确定每个块每帧执行一次?有没有一个分支遗忘的替代方案(值得怀疑,假设这些块中有足够不同的逻辑)?n

请注意,将进行全面优化,没有太大区别(如果有的话)。switch

C++ 优化 分支预测

评论

0赞 Mark Ransom 5/7/2014
请允许我问一个显而易见的问题:这是否在代码中的一部分会产生明显的差异?
0赞 Luchian Grigore 5/7/2014
@MarkRansom是的。如果存在替代方案,我对此表示怀疑,但我对此充满希望。
0赞 harold 5/7/2014
这似乎是 for/switch 模式的微妙变化,只是帧在两者之间“结束”。主循环是如何工作的?是否有可能通过以下方式展开它?n
0赞 interjay 5/7/2014
是已知常数吗?如果是这样,您可以展开循环,尽管它会很丑陋。n
0赞 Luchian Grigore 5/7/2014
@interjay CC. Harold - 无法展开循环,这些需要每帧发生(有一个主控制循环在单独的线程上运行)

答:

1赞 milianw 5/7/2014 #1

正如我上面所评论的,在没有任何代码示例的情况下,我想很难在这里提供任何有用的帮助。您能否发布一个显示大量分支未命中的代码片段?

我只是尝试了这样的事情:

#include <cstdlib>

__attribute__ ((noinline)) void frame(const int frame) // to prevent automatic unrolling
{
  const int n = 10;
  static int someVar = rand();
  static int someOtherVar = rand();

  const int currentFrame = frame % n;

  if (currentFrame == 0) {
    someVar = frame;
  } else if (currentFrame == 1) {
    someOtherVar += frame;
  } else if (currentFrame == 2) {
    someOtherVar -= someOtherVar;
    someVar = someOtherVar;
  } else if (currentFrame == 3) {
    someVar -= someOtherVar;
  } else if (currentFrame == 4) {
    someVar -= someOtherVar;
    someOtherVar *= someOtherVar;
  } else if (currentFrame == 5) {
    someOtherVar /= someVar + frame;
  } else if (currentFrame == 6) {
    someVar *= someOtherVar - frame;
  } else if (currentFrame == 7) {
    someOtherVar += someVar / (someOtherVar + 1);
  } else if (currentFrame == 8) {
    someVar -= someOtherVar * someVar;
  } else if (currentFrame == n - 1) {
    someOtherVar = frame;
    someVar = frame + 1;
  }
}

int main(int argc, char** argv)
{
  int iterations = 100000000;
  if (argc > 1) {
    iterations = std::atoi(argv[1]);
  }

  for (int i = 0; i < iterations; ++i) {
    frame(i);
  }

  return 0;
}

但这并不能复制您的发现:

Performance counter stats for './a.out 100000000':

        591.088374      task-clock (msec)         #    0.999 CPUs utilized          
                60      context-switches          #    0.102 K/sec                  
                5      cpu-migrations            #    0.008 K/sec                  
              272      page-faults               #    0.460 K/sec                  
    1,665,803,234      cycles                    #    2.818 GHz                     [50.25%]
  <not supported>      stalled-cycles-frontend  
  <not supported>      stalled-cycles-backend   
    3,741,605,478      instructions              #    2.25  insns per cycle         [75.14%]
    1,050,201,459      branches                  # 1776.725 M/sec                   [75.14%]
            11,115      branch-misses             #    0.00% of all branches         [74.64%]

      0.591689393 seconds time elapsed

评论

0赞 interjay 5/7/2014
对于 n=4,我认为分支预测器将检测到该模式。对于较大的 n,你会得到更多的失误。
0赞 milianw 5/7/2014
如果它能检测到 4 的模式,为什么它不应该检测到 10 的模式呢?更新了代码 - 相同的行为。
1赞 Mark Ransom 5/7/2014
检查程序集输出。可能是编译器优化了所有情况,因为没有使用任何计算结果。
0赞 interjay 5/7/2014
分支预测器用于存储每个分支的模式历史记录的存储空间有限,因此存在上限。我不知道它在现代处理器中会有多大。
1赞 milianw 5/7/2014
本地增加到 20,没有区别。再次表明我们需要从运算中对 n 等的大小进行一些输入。 第 @MarkRansom 页:Assembly 显示分支在那里。此外,从 perf 报告的分支数量取决于我使用的条件数。