【问题标题】:Portable branch prediction hints便携式分支预测提示
【发布时间】:2011-04-11 19:13:33
【问题描述】:

是否有任何可移植的方式来进行分支预测提示?考虑以下示例:

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

这与做有什么不同:

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

或者是使用编译器特定提示的唯一方法? (例如 GCC 上的 __builtin_expect)

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

【问题讨论】:

  • 我想知道这是否可能是 C++0x 属性在if 的条件下坚持的东西?喜欢if([[unlikely]] unlikely_condition) { ... }?目前语法不允许。但它确实允许if([[unlikely]] bool b = ...) { }。也许有人会滥用它:)
  • GNU 代码在完全非性能关键代码中包含大量可笑的if(likely(...)) 垃圾,IMO 这真的很糟糕。一方面,它在英语中读起来不自然——听起来像是“如果这个条件很可能是真的”而不是“如果这个条件是真的,它很可能是”。另一方面,它只是杂乱无章。除非您有很多性能关键条件无法编译为 cmov 或类似条件,否则请忽略分支预测提示。
  • @R.. 我想我明白为什么 Linux 内核中到处都是if(unlikely(...))。他们更喜欢让代码流更容易遵循的早期退出。如果他们不这样做,那么静态分支预测总是会失败。
  • 这会使 Linux 慢 0.00001%。不可测量。如果是这样,只需将这些废话放在几个可测量的条件句中,而不是无处不在。
  • 也是一种文档提示。我经常用它来区分活动工作代码和异常错误处理代码。这在我工作的架构上说,这是一个非常有用的机制,因为 ISA 在分支指令 (SPARC) 中有提示位。

标签: c++ c optimization c++20 branch-prediction


【解决方案1】:

进行静态分支预测的规范方法是预测if 是非分支的(即每个if 子句都被执行,而不是else),并采用循环和backward-gotos。因此,如果您期望静态预测很重要,请不要将常见情况放在else 中。绕过未使用的循环并不容易。我从未尝试过,但我想把它放在一个 else 子句中应该可以很方便地工作。

许多编译器支持某种形式的#pragma unroll,但仍然需要使用某种#if 来保护它以保护其他编译器。

分支预测提示理论上可以完整描述如何转换程序的流控图并在可执行内存中排列基本块……所以要表达的东西多种多样,而且大多数都不是很便携。

正如 GNU 在 __builtin_expect 的文档中所建议的那样,配置文件引导的优化优于提示,而且工作量更少。

【讨论】:

  • 而且 VS 也有 PGO,所以这是双赢的。 :)
【解决方案2】:

大多数情况下,如下代码

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'代码放在大括号中——即使它是单行的。否则有点混乱!
  • "尽量尽早评估 A,以便 CPU 可以动态优化流水线。" - 你能为这个声明提供一些指导吗?我想知道它是怎么做到的。
  • @LiorKogan 链接现已失效。有什么办法可以找回来吗?
  • @Carlos:是的。谷歌“英特尔 24281603”。
【解决方案3】:

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

所以没有编译器特定的扩展你能做的最好的事情就是组织你的代码,让你的编译器在没有帮助的情况下“做正确的事”。但是,如果您想确定,请使用编译器扩展。 (你可以尝试在预处理器后面抽象它们,这样你的代码就可以移植了。)

【讨论】:

  • 尽管提供优化提示的语言有很多先例(inlinerestrictregister)。在现代编译器中,有些比其他的更有意义。一个特定的实现是否真的对它们做了什么并不重要:如果有合理的机会做一些有用的事情,那么这将是一个很好的特性。我认为主要的静态分支预测不符合该标准,因此我认为将其排除在外并不是什么坏事。不过,这是对案件是非曲直的判断,并不完全是“我们永远不关心优化”。
  • @Steve:是的,我想我会自动忽略这些,所以我忘记了它们。你是对的。
  • 我认为restrict 作为一个过早的优化可能是值得的。它不会造成任何伤害,它可能会通过防止可怕的存储/加载依赖关系来带来显着的好处,并且它在应用的地方可能是一个记录的要求(如memcpy),无论这是否反映在源代码中。其他人(以及在 C++03 中),我同意你响亮的“meh”:-)
  • @Steve:我同意,我忽略了restrict,因为它不是当前标准的一部分。 :)
  • 啊,但这是标记为 C 和 C++ 的问题之一,即使是最简单的答案也会在大量警告中消失。
【解决方案4】:

C++20 提供likely and unlikely attributes

允许编译器针对执行路径的情况进行优化 包括该陈述的可能性或多或少比任何替代方案 不包含此类语句的执行路径

【讨论】:

【解决方案5】:

只要与你所做的保持一致。我喜欢用

if (!(someExpression))

但编译器应该平等对待。

【讨论】:

    【解决方案6】:

    通过#ifdef 检查特定编译器并将这些内容隐藏在自定义宏后面有什么问题?如果您没有支持这些优化提示的编译器,您可以#define 将其扩展为纯表达式。我最近对 ​​GCC 通过内部函数支持的显式缓存预取做了类似的事情。

    【讨论】:

    • 我已经做到了。如果不同的编译器使用不同的语法,那么如果使用两个或三个以上的编译器,就会变得一团糟。
    猜你喜欢
    • 2018-06-15
    • 2020-12-16
    • 2013-05-19
    • 1970-01-01
    • 2014-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多