C++ 通过重新排序优化分支

C++ 通过重新排序优化分支,c++,c,optimization,performance,C++,C,Optimization,Performance,我有一个C函数,它被调用了无数次: void foo () { if (/*condition*/) { } else if(/*another_condition*/) { } else if (/*another_condition_2*/) { } /*And so on, I have 4 of them, but we can generalize it*/ else {

我有一个C函数,它被调用了无数次:

void foo ()
{
    if (/*condition*/)
    {

    }
    else if(/*another_condition*/)
    {

    }
    else if (/*another_condition_2*/)
    {

    } 
          /*And so on, I have 4 of them, but we can generalize it*/
    else
    {

    }
 }
我有一个很好的测试用例来调用这个函数,导致某些if分支被调用的次数多于其他分支

我的目标是找出排列if语句的最佳方法,以最小化分支

我能想到的唯一方法是对每个if条件分支到的文件进行写入,从而创建一个直方图。这似乎是一种乏味的方式。有没有更好的方法,更好的工具


我正在AS3Linux上构建它,使用GCC3.4;使用oprofile(opcontrol)进行评测。

试试评测器(gprof?)-它会告诉你花费了多少时间。我不记得gprof是否对分支进行计数,但如果没有,只需在每个分支中调用一个单独的空方法。

将每个分支中的代码包装到一个函数中,并使用探查器查看每个函数被调用的次数。

逐行分析可以让您了解哪些分支被调用的频率更高


使用类似的功能可以自动进行此优化。

它不可移植,但许多版本的GCC支持一个名为
\u builtin\u expect()
的函数,该函数可用于告诉编译器我们期望的值:

if(__builtin_expect(condition, 0)) {
  // We expect condition to be false (0), so we're less likely to get here
} else {
  // We expect to get here more often, so GCC produces better code
}
Linux内核将这些用作宏,以使它们更直观、更干净、更可移植(即在非GCC系统上重新定义宏):

有了这个,我们可以重写上面的内容:

if(unlikely(condition)) {
  // we're less likely to get here
} else {
  // we expect to get here more often
}

当然,这可能是不必要的,除非你的目标是原始速度和/或你已经分析并发现这是一个问题。

一些计数器可能会有所帮助。看到计数器后,如果差异很大,可以按降序对条件进行排序

static int cond_1, cond_2, cond_3, ... void foo (){ if (condition){ cond_1 ++; ... } else if(/*another_condition*/){ cond_2 ++; ... } else if (/*another_condtion*/){ cond_3 ++; ... } else{ cond_N ++; ... } }
我认为只修改包含foo()函数的文件就足够了。

在下运行程序将为您提供分支信息。另外,我希望您分析并确定这段代码有问题,因为这充其量只是一个微优化。编译器将从if/else if/else if/else if生成一个分支表,它不需要分支(这显然取决于条件是什么)0,甚至处理器上的分支预测器也不需要分支(假设这不是嵌入式工作,如果可以忽略我的话)非常擅长确定分支的目标。

实际上,在我看来,将它们更改为什么顺序并不重要。分支预测器将存储最常见的分支并自动执行

也就是说,有些东西你可以试试。。。您可以维护一组作业队列,然后根据if语句将它们分配到正确的作业队列,然后在最后一个接一个地执行它们

这可以通过使用条件移动等进一步优化(尽管这需要汇编程序,AFAIK)。这可以通过有条件地将1移动到寄存器(在条件a中初始化为0)中来实现。将指针值放在队列的末尾,然后通过向计数器添加条件1或0来决定是否增加队列计数器


突然间,您消除了所有分支,并且有多少分支预测失误变得无关紧要。当然,就像这些事情一样,你最好不要进行分析,因为,尽管它看起来会带来一场胜利。。。可能不会。我们使用的机制如下:

// pseudocode
class ProfileNode
{
public:
   inline ProfileNode( const char * name ) : m_name(name)
   {  }
   inline ~ProfileNode()
   {
      s_ProfileDict.Find(name).Value() += 1; // as if Value returns a nonconst ref
   }

   static DictionaryOfNodesByName_t  s_ProfileDict;
   const char * m_name; 
}
然后在你的代码里

void foo ()
{
    if (/*condition*/)
    {
       ProfileNode("Condition A");
       // ...
    }
    else if(/*another_condition*/)
    {
       ProfileNode("Condition B");
       // ...
    } // etc..
    else
    {
       ProfileNode("Condition C");
       // ...
    }
 }

void dumpinfo()
{
  ProfileNode::s_ProfileDict.PrintEverything();
}
您可以看到在这些节点中放置秒表计时器是多么容易,并且可以看到哪些分支消耗的时间最多。

作为一种分析技术,这是我所依赖的

您想知道的是:评估这些条件所花费的时间是否是执行时间的重要部分

样本会告诉你,如果不是,那就没关系了


如果它真的很重要,例如,如果条件包括堆栈上大部分时间的函数调用,那么您要避免的是花费大量时间进行false比较。判断的方法是,如果您经常看到它从第一个或第二个if语句调用比较函数,那么在这样的示例中捕获它并跳出它,查看它是否返回false或true。如果它通常返回false,那么它可能会在列表中更靠后。

这是一个令人毛骨悚然的用户名……按顺序排序并不一定保证编译器会按给定的顺序输出它们,尽管我认为这不会有什么坏处。查看我的答案,可以找到一种更好的排序方法。每个分支中都有一个单独的空方法。如果我正在构建一个优化的二进制文件,怎么能告诉编译器不要内联函数呢?你要做的就是看看哪个分支执行得最频繁。构建未优化,并获得data@Andrei:或指定noinline函数属性。函数+1的解释很好。不过,我想回应你的“这可能没有必要”。对于任何经常发生的分支(如果分支不经常发生,您可能不关心错误分支对性能的影响),处理器的分支预测器在没有这些提示的情况下通常已经做得足够好了。值得一提的是,在Linux内核之类的东西中,分支的性能影响是需要考虑的问题。然而,对于大多数项目,您是对的:分支不会成为瓶颈。+1是一个深思熟虑的答案,但坦率地说,它不取决于在每个条件下花费的周期,以及在最终选择的分支中花费的周期吗?在分支预测产生显著差异之前,这些必须非常简单。当然,这会带来一些开销,如果调用引入的函数一百万次,您可以更改所看到的计时…是的,在实践中,我们对节点标识符使用常量整数符号,而不是字符串,使字典查找实际上成为一个O(1)数组索引。
// pseudocode
class ProfileNode
{
public:
   inline ProfileNode( const char * name ) : m_name(name)
   {  }
   inline ~ProfileNode()
   {
      s_ProfileDict.Find(name).Value() += 1; // as if Value returns a nonconst ref
   }

   static DictionaryOfNodesByName_t  s_ProfileDict;
   const char * m_name; 
}
void foo ()
{
    if (/*condition*/)
    {
       ProfileNode("Condition A");
       // ...
    }
    else if(/*another_condition*/)
    {
       ProfileNode("Condition B");
       // ...
    } // etc..
    else
    {
       ProfileNode("Condition C");
       // ...
    }
 }

void dumpinfo()
{
  ProfileNode::s_ProfileDict.PrintEverything();
}