编译器是否有一定的优化启发式来支持分支预测?如果没有,为什么不呢?

时间:2013-01-03 23:20:07

标签: c++ c compiler-optimization cpu-architecture

这个问题主要是Aater Suleman在阅读this article后从软件方面改进分支预测的后续工作。作者提供了一种“展开”条件语句的方法,以增加在2位饱和计数器方案的情况下预测分支的概率。这是一个exerpt:

  

让我举个例子来解释一下。让我们假设X是0到99之间的随机变量。我想运行以下代码:

     
    

如果(X> 5&& X< 95)//分支占90%的时间
    do_something();

  
     

但是,如果我将代码编写为:

     
    

如果(X> 5)//分支占95%的时间
    if(X <95)//分支占95%的时间
    do_something();

  
     

分支预测器更准确地预测这两个分支,这可能会导致更好的性能,因为分配给这两个分支的计数器更有可能在采取时保持饱和(因为两个未采用的可能性较小)。

     

一般情况下,只要你在if语句中使用ANDing / ORing条件,就应该考虑组合是偏向偏差还是偏差较小,并选择更偏向的版本。

我的问题是:编译器是否一直遵循这种启发式方法?由于编译器存在于ISA的范围内,并且架构和分支预测方案存在于处理器范围和更具体的硬件实现中,编译器是否甚至有权执行类似的操作?

我的直觉是,以这种方式扩展控制语句不会损害性能,但与此同时,我无法找到编译器进行此类优化的任何证据。如果是这样的话,为什么不呢?我在推理中缺少什么,有人可以提供一个例子,这样的优化会对某个架构或预测方案产生不利影响吗?

感谢。

2 个答案:

答案 0 :(得分:4)

苏莱曼似乎并不知道

if (X > 5 && X < 95)
  do_something();

if(X > 5)
  if(X < 95)
    do_something();

在C和C ++中在语义上是等价的。最新的C标准规定 (6.5.13 / 4):

  

与按位二进制&amp;操作员,&amp;&amp;运营商保证   从左到右的评价;如果评估第二个操作数,则有   第一个和第二个的评估之间的序列点   操作数。如果第一个操作数比较等于0,则第二个操作数   没有评估。

编译器可能会也可能不会为两个代码示例生成相同的代码。重写您的程序以使用一种或另一种形式来提高性能并不是一个好主意。结果将在很大程度上取决于编译器版本,ISA以及可能更多的变量。将这种类型的优化留给编译器,但为编译器提供所需的信息。

像GCC和LLVM这样的一些编译器允许你给出这样的明确提示:

if (__builtin_expect(X > 5, 1)) {
  // This block is likely to be taken.
}
if (__builtin_expect(X <= 5, 0)) {
  // This block is unlikely to be taken.
}

另一种方法是使用配置文件引导优化。第一步需要程序的一次或多次测试运行,以生成包含有关分支指令的统计信息的数据库。然后,编译器可以使用此数据库来优化您的程序。有关更多详细信息,请参阅编译器手册。

答案 1 :(得分:1)

您可以通过用

替换此代码来避免这两个分支
if (((unsigned int) X) - 6 < 88)
    do_something ();

__builtin_expect显然无法改变条件是真还是假。它主要用于处理器,其中采用的分支比未采用的分支慢。所以编译器会编译

if (__builtin_expect (condition, 0))
    statement;

morestuff ();

到此:

if (condition) goto elsewhere;
backhere: morestuff ();

....

elsewhere: statement; goto backhere;

因此通常没有采取分支,而不是通常的

if (! condition) goto nextstatement;
statement;
nextstatement: morestuff ();

但在问题中,事情并非如此。 if(cond1&amp;&amp; cond2)不会生成单个分支(仅在具有非常聪明的编译器的特殊情况下),因此比较无效。如果是这样,两个具有95%正确预测的分支并不比一个具有90%的分支更好,因为错误预测的数量将是相同的。