便携式分支预测提示

44

有没有一种可移植的方法来进行分支预测提示?考虑下面的例子:

  if (unlikely_condition) {
    /* ..A.. */
  } else {
    /* ..B.. */
  }

这与做下面的操作有什么不同:

  if (!unlikely_condition) {
    /* ..B.. */
  } else {
    /* ..A.. */
  }

还有其他方法可以达到类似于GCC中的 __builtin_expect 这样的编译器特定提示吗?

编译器是否会根据条件的顺序不同而对 if 条件进行不同的处理?


5
GNU代码在非性能关键代码中包含了过多的if(likely(...))废物,我认为这真的很糟糕。首先,在英语中它不太自然——听起来像是“如果这个条件可能为真”,而不是“如果这个条件为真,那么它可能是”。另外,这只是杂乱无章的东西。除非你有大量的性能关键条件语句无法编译到cmov或类似指令,否则可以忽略分支预测提示。 - R.. GitHub STOP HELPING ICE
1
@R.. 我想我明白为什么Linux内核到处都是if(unlikely(...))。他们更喜欢早期退出,使代码流程更容易跟踪。如果他们不这样做,那么静态分支预测将总是失败。 - Oskar N.
1
而且它会使Linux变慢0.00001%。无法测量。如果可以的话,只需将此垃圾放在可测量的少数条件语句中,而不是到处都是。 - R.. GitHub STOP HELPING ICE
4
这也是一种文档提示。我经常使用它来区分活动工作代码和异常错误处理代码。就我所在的架构而言,这是一个非常有用的机制,因为ISA在分支指令中具有提示位(SPARC)。 - Patrick Schlüter
@tristopia:MIPS 也有类似的东西:分支预测指令。 - ninjalj
显示剩余2条评论
6个回答

32

进行静态分支预测的经典方法是将 if 预测为非分支 (即执行每个 if 子句而不是 else),并且循环和向后跳转语句会被执行。因此,如果你希望静态预测起到重要作用,则不要将通用情况放在 else 中。解决未执行循环的问题并不容易;我从未尝试过,但我认为将其放在 else 子句中应该能够很方便地实现。

许多编译器都支持某种形式的 #pragma unroll,但仍需要用一些 #if 保护它以防止其他编译器。

分支预测提示理论上可以表达如何转换程序的控制流图并安排基本块在可执行内存中的完整描述... 因此有许多要表达的内容,大多数不太可移植。

正如 GNU 在 __builtin_expect 文档中建议的那样,基于剖面优化比提示更加优秀,并且需要的工作量更少。


而且VS也有PGO,所以这是双赢。 :) - GManNickG

21

在大多数情况下,以下代码

if (a)
{
   ...
}
else
{
    ...
}

实际上是

evaluate(A)

if (!A)
{
   jmp p1
}

... code A

   jmp p2

p1:

... code !A

p2:

注意,如果A为真,则“代码A”已经在流水线中。处理器将看到“jmp p2”命令,加载p2代码到流水线。

如果A为假,则“代码!A”可能不在流水线中,因此速度可能较慢。

结论:

  1. 如果X比!X更有可能发生,请执行If(X)
  2. 尽早评估A,以便CPU可以动态优化流水线。

:

evaluate(A)

do more stuff

if (A)
   ...

明白了!请保留大括号中的“if”代码,即使它只有一行。否则会有点混淆! - Ayyappa
1
尽早评估A,以便CPU可以动态优化流水线。您能否提供有关此语句的一些指针?我想知道它将如何实现。 - Ayyappa
这里有很多信息:http://download.intel.com/design/pentiumii/manuals/24281603.pdf - Lior Kogan
@LiorKogan 的链接现在已经失效了。有没有重新找到它的办法? - Carlos
1
@Carlos:是的。谷歌搜索“Intel 24281603”。 - Lior Kogan

7

优化本质上是编译器的事情,因此您必须使用编译器功能来帮助它。语言本身并不关心(或强制执行)优化。

因此,在没有特定于编译器的扩展的情况下,最好的方法是以这样一种方式组织您的代码,使得您的编译器可以“自动完成正确的事情”。但如果您想确保,请使用编译器扩展。(您可以尝试在预处理器中将它们抽象化,以便您的代码保持可移植性。)


6
语言中有很多关于提供优化提示的先例,比如(inlinerestrictregister),尽管现代编译器上有些比其他一些更有意义。某个特定的实现是否真正利用这些提示并不重要:如果有合理的机会会有所作为,那么它就是一个好功能。我认为,在主体中使用静态分支预测并不符合这个标准,所以我认为不将其包含在内并不是一个坏决定。虽然这是一个评估案件的判断,但并不完全是“我们永远不关心优化”。 - Steve Jessop
@Steve:是的,我想我自动忽略了那些东西,所以我忘记了它们。你说得对。 - GManNickG
2
我认为restrict作为一种过早的优化可能是值得的。它不会造成任何伤害,通过防止可怕的存储/加载依赖关系,它可能会产生显着的好处,并且在适用的情况下,它很可能是一个文档要求(如memcpy),无论是否反映在源代码中。其他的(在C++03中),我同意你的“meh” :-) - Steve Jessop
@Steve:我同意,我忽略了restrict,因为它不是当前标准的一部分。 :) - GManNickG
啊,但这是一个标记为C和C ++的问题之一,即使是最简单的答案也可能会在充斥着警告的暴风雪中消失。 - Steve Jessop
@Steve:哦,我甚至没有看到那个。 :x - GManNickG

7

C++20提供likely和unlikely属性

允许编译器针对包括该语句的执行路径比不包括该语句的任何替代执行路径更或多或少可能的情况进行优化


很遗憾,这个功能目前在Clang中还不可用。[https://clang.llvm.org/cxx_status.html#cxx20] - Franklin Yu
1
@FranklinYu 看起来Clang 12支持这个,参见https://en.cppreference.com/w/cpp/compiler_support。 - Tony
这是个好消息!感谢更新。 - Franklin Yu

1

通过#ifdef检查特定编译器有什么问题,并将这些内容隐藏在自定义宏后面?您可以将其#define扩展到普通表达式中,在没有支持这些优化提示的编译器的情况下进行。我最近使用内部函数对显式缓存预取进行了类似的操作,GCC支持此操作。


我已经做到了。如果不同的编译器使用不同的语法,使用两个或三个以上的编译器可能会变得非常混乱。 - David Thornley

1

保持你所做的一致。我喜欢使用

if (!(someExpression))

但编译器应该同等对待这个。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接