24

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

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
}

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

4

2 回答 2

20

你读过 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 概念,其中 true 和 false 代码是相等的......你必须分支才能找到代码非此即彼。完成的方式基本上是伪代码:

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

鉴于大多数 CPU 跟随gotodown 到else_return_39:标签比仅仅下降到更慢return 10 * y,“true”分支的代码将比 false 分支更快地到达。当然,机器代码可以测试 x 是否为0,将“假”代码 ( return 39) 放在首位,从而反转性能特征。

这是 __builtin_expect 控制的 - 您可以告诉编译器将真或假分支放在需要较少分支才能到达它的地方,从而获得微小的性能提升。

但这适用于 a) 内联 ifs、b) 变量和 c) 0 和 1 以外的值吗?

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

[您的评论表明您对条件表达式感兴趣a ? b : c--我不确定-在我是否可以在 C 中使用 GCC 的 __builtin_expect() 和三元运算符时对该问题有争议的答案,这可能证明以一种或另一种方式很有见地,或者进一步探索的基础]

b)变量 - 你假设:

int x = __builtin_expect( t / 10, 7 );
if( x == 7 ) {

那是行不通的——编译器没有义务将这些期望与变量相关联,并在下次if看到 an 时记住它们。您可以使用生成汇编语言输出来验证这一点(就像我对 gcc 3.4.4 所做的那样)gcc -S:无论预期值如何,程序集都不会改变。

c) 0 和 1 以外的值

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

您应该使用诸如

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

在测试指针或浮点值时。

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

于 2013-03-18T01:42:59.363 回答
15

但这适用于 a) 内联 ifs、b) 变量和 c) 0 和 1 以外的值吗?

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

所以,a) 是的。b) 没有。 c) 是的。

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

是的!

它利用了使用指令流水线的架构,允许 CPU 在当前指令完成之前开始处理即将到来的指令。

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

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

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

__builtin_expect允许(不保证)gcc 尝试组装代码,因此可能的情况涉及比替代方案更少的跳转。

于 2013-03-18T01:28:41.237 回答