GCC __builtin_expect走了多远?

时间:2013-03-18 00:33:05

标签: c++ c optimization gcc

在回答另一个问题时,我对此感到好奇。我很清楚

if( __builtin_expect( !!a, 0 ) ) {
    // not likely
} else {
    // quite likely
}

通过做一些暗示处理器/更改汇编代码顺序/某种魔术的东西,可以更快地(通常)使“非常可能”分支更快。 (如果有人能澄清那种也很棒的魔法)。

但这是否适用于a)内联ifs,b)变量和c)除0和1以外的值?即将

__builtin_expect( !!a, 0 ) ? /* unlikely */ : /* likely */;

int x = __builtin_expect( t / 10, 7 );
if( x == 7 ) {
    // likely
} else {
    // unlikely
}

if( __builtin_expect( a, 3 ) ) {
    // likely
    // uh-oh, what happens if a is 2?
} else {
    // unlikely
}

有什么影响吗?所有这些都取决于目标架构吗?

2 个答案:

答案 0 :(得分:18)

您是否阅读过GCC文档?

  

内置函数:long __builtin_expect(long exp,long c)

     

您可以使用__builtin_expect为编译器提供分支   预测信息。一般来说,您应该更喜欢使用实际   正如程序员所做的那样(-fprofile-arcs)的配置文件反馈   众所周知,在预测他们的计划实际执行情况方面表现不佳。   但是,有些应用程序很难收集这些数据。

     

返回值是exp的值,它应该是一个整数   表达。内置的语义是预期的   exp == c。例如:

if (__builtin_expect (x, 0))
    foo ();
     

表示我们不希望调用foo,因为我们期望x为零。由于您仅限于exp的整数表达式,因此应使用

等结构
if (__builtin_expect (ptr != NULL, 1))
    foo (*ptr);
     

测试指针或浮点值时。

要解释一下...... __builtin_expect对于传达您认为该程序可能采用的分支特别有用。您询问编译器如何使用该洞察力 - 好吧,请考虑以下代码:

if (x == 0)
    return 10 * y;
else
    return 39;

在机器代码中,通常可以要求CPU“转到”另一行(这需要时间,并且取决于CPU可能会阻止其他执行优化 - 例如,在机器代码级别之下 - 请参阅分支标题在http://en.wikipedia.org/wiki/Instruction_pipeline)之下,或者调用其他一些代码,但实际上并没有if / else概念,其中真假代码都是相等的......你必须分开去寻找其中一个或另一个的代码。完成的方式基本上是伪代码:

test whether x is 0
if it was goto else_return_39
return 10 * y
else_return_39:
return 39

鉴于大多数CPU在goto下降到else_return_39:标签后速度慢于直到return 10 * y,因此“真正”分支的代码将比假枝。当然,机器代码可以测试x是否 0,首先放置“假”代码(return 39),从而扭转性能特征。

这就是__builtin_expect所控制的 - 你可以告诉编译器将真分支或假分支放在需要较少分支的位置,从而获得微小的性能提升。

  

但这是否适用于a)内联ifs,b)变量和c)除0和1以外的值?

a)周围函数是否内联并不会改变出现if语句的分支需求(除非优化器看到if语句测试的条件总是{{1或true只有一个分支永远不会运行)。因此,它同样适用于内联代码。

[您的评论显示您对条件表达式感兴趣 - false - 我不确定 - 在Can I use GCC's __builtin_expect() with ternary operator in C处对该问题提出的争议答案可能会以某种方式证明其具有洞察力,或者进一步探索的基础]

b)变量 - 你假设:

a ? b : c

这不起作用 - 编译器没有义务将这些期望与变量联系起来,并在下次看到int x = __builtin_expect( t / 10, 7 ); if( x == 7 ) { 时记住它们。您可以使用if来验证这一点(就像我对gcc 3.4.4所做的那样)来生成汇编语言输出:无论期望值如何,程序集都不会改变。

c)0和1以外的值

它适用于整数(gcc -S)值,所以是的。上面引用的文档的最后一段解决了这个问题,特别是:

  

你应该使用诸如

之类的结构
long
     

测试指针或浮点值时。

为什么呢?好吧,如果指针类型大于if (__builtin_expect (ptr != NULL, 1)) foo (*ptr); ,那么调用long将有效地切断一些不太重要的位,并且无法将其余部分合并到测试中。同样,浮点值可能大于long,转换不会产生您期望的结果。通过使用布尔表达式,例如__builtin_conversion(long, long)(给定ptr != NULL转换为1L而true转换为0),您肯定会得到预期的结果。

答案 1 :(得分:11)

  

但这是否适用于a)内联ifs,b)变量和c)除0和1以外的值?

它适用于用于确定分支的表达式上下文。

所以,a)是的。 b)否.c)是。

  

所有这些都取决于目标架构吗?

是的!

它利用使用instruction pipelining的架构,允许CPU在当前指令完成之前开始处理即将发出的指令。

  

(如果有人能澄清那种也很棒的魔法)。

(“分支预测”使这种描述复杂化,所以我有意省略它)

任何类似于if语句的代码都意味着表达式可能导致CPU跳转到程序中的其他位置。这些跳转使CPU的指令管道中的内容无效。

__builtin_expect允许(不保证)gcc尝试汇编代码,因此可能场景的跳转次数少于备用场景。