是否有编译器提示 GCC 强制分支预测总是按照某种方式进行?

对于 Intel 体系结构,是否有一种方法可以指示 GCC 编译器生成代码,这些代码总是强制在我的代码中以特定的方式进行分支预测?英特尔的硬件支持这个吗?其他编译器或硬件如何?

我会在 C + + 代码中使用这种方法,在这种情况下,我知道我希望快速运行,并且不关心其他分支需要执行时的慢速,即使它最近已经执行了该分支。

for (;;) {
if (normal) { // How to tell compiler to always branch predict true value?
doSomethingNormal();
} else {
exceptionalCase();
}
}

作为 Evdzhan Mustafa 的后续问题,这个提示是否可以在处理器第一次遇到指令时指定一个提示,所有后续的分支预测都能正常工作?

29460 次浏览

GCC supports the function __builtin_expect(long exp, long c) to provide this kind of feature. You can check the documentation 给你.

其中 exp是使用的条件,c是期望值。例如,在您的情况下,您想要

if (__builtin_expect(normal, 1))

由于语法比较笨拙,这通常用于定义两个自定义宏,如

#define likely(x)    __builtin_expect (!!(x), 1)
#define unlikely(x)  __builtin_expect (!!(x), 0)

只是为了简化任务。

记住:

  1. 这不符合标准
  2. a compiler/cpu branch predictor are likely more skilled than you in deciding such things so this could be a premature micro-optimization

Gcc 有 Long _ _ builtin _ Expect (long exp,long c)(强调我的) :

您可以使用 _ _ builtin _ Expect 为编译器提供分支 一般来说,< strong > 应该更喜欢使用 real profile feedback for this (-fprofile-arcs), as programmers are 众所周知,他们不善于预测程序的实际执行情况。 但是,在一些应用程序中很难收集这些数据。

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

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

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

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

当测试指针或浮点值时。

正如文档指出的那样,您应该更喜欢使用实际的配置文件反馈和 本文给出了一个实际的例子,以及在他们的情况下,它如何至少比使用 __builtin_expect有所改进。请参阅 如何在 g + + 中使用配置文件引导的优化?

我们还可以找到一个使用这个特性的 Linux 内核新手关于内核宏的文章可能() ,也可能():

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

注意在宏中使用的 !!,我们可以在 为什么用! ! (条件)代替(条件) ?中找到对此的解释。

仅仅因为 Linux 内核中使用了这种技术,并不意味着使用它总是有意义的。我们可以从这个问题中看到,我最近回答的 将参数作为编译时间常数或变量传递时,函数性能之间的差异,许多手摇优化技术在一般情况下不工作。我们需要仔细分析代码,以了解一种技术是否有效。许多旧的技术甚至可能与现代编译器优化无关。

注意,虽然内置程序不是可移植的 Clang 也支持 _ _ builtin _ Expect

还有一些 它可能不会有什么不同

_ _ builtin _ Expect 可以用来告诉编译器你希望一个分支走哪条路。这会影响代码的生成方式。典型的处理器顺序运行代码的速度更快。所以如果你写

if (__builtin_expect (x == 0, 0)) ++count;
if (__builtin_expect (y == 0, 0)) ++count;
if (__builtin_expect (z == 0, 0)) ++count;

编译器将生成如下代码

if (x == 0) goto if1;
back1: if (y == 0) goto if2;
back2: if (z == 0) goto if3;
back3: ;
...
if1: ++count; goto back1;
if2: ++count; goto back2;
if3: ++count; goto back3;

如果您的提示是正确的,这将在没有实际执行任何分支的情况下执行代码。它将比正常序列运行得更快,其中每个 if 语句将围绕条件代码分支,并执行三个分支。

较新的 x86处理器具有针对预期将被采用的分支的指令,或针对预期不会被采用的分支的指令(有一个指令前缀; 具体细节不确定)。不确定处理器是否使用这个。它不是很有用,因为分支预测可以很好地处理这个问题。所以我不认为你能真正影响 预测分支。

不,没有。(至少在现代 x86处理器上是这样。)

其他答案中提到的 __builtin_expect影响 gcc 安排汇编代码的方式。当然,重新排序代码会对分支预测产生间接影响。但是在现代的 x86处理器上,没有指令告诉 CPU“假设这个分支被占用了/没有被占用”。

See this question for more detail: 英特尔 x860x2E/0x3E 前缀分支预测实际使用?

To be clear, __builtin_expect and/or the use of -fprofile-arcs 可以 improve the performance of your code, both by giving hints to the branch predictor through code layout (see Performance optimisations of x86-64 assembly - Alignment and branch prediction), and also improving cache behaviour by keeping "unlikely" code away from "likely" code.

As the other answers have all adequately suggested, you can use __builtin_expect to give the compiler a hint about how to arrange the assembly code. As 官方文件 point out, in most cases, the assembler built into your brain will not be as good as the one crafted by the GCC team. It's always best to use actual profile data to optimize your code, rather than guessing.

类似的,但还没有提到的,是一种 GCC 特有的方法,强制编译器在“冷”路径上生成代码。这涉及到 noinlinecold属性的使用,这些属性完全按照它们听起来的样子进行操作。这些属性只能应用于函数,但是使用 C + + 11,您可以声明内联 lambda 函数,并且这两个属性也可以应用于 lambda 函数。

Although this still falls into the general category of a micro-optimization, and thus the standard advice applies—test don't guess—I feel like it is more generally useful than __builtin_expect. Hardly any generations of the x86 processor use branch prediction hints (reference), so the only thing you're going to be able to affect anyway is the order of the assembly code. Since you know what is error-handling or "edge case" code, you can use this annotation to ensure that the compiler won't ever predict a branch to it and will link it away from the "hot" code when optimizing for size.

使用方法:

void FooTheBar(void* pFoo)
{
if (pFoo == nullptr)
{
// Oh no! A null pointer is an error, but maybe this is a public-facing
// function, so we have to be prepared for anything. Yet, we don't want
// the error-handling code to fill up the instruction cache, so we will
// force it out-of-line and onto a "cold" path.
[&]() __attribute__((noinline,cold)) {
HandleError(...);
}();
}


// Do normal stuff
⋮
}

Even better, GCC will automatically ignore this in favor of profile feedback when it is available (e.g., when compiling with -fprofile-use).

See the official documentation here: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

对于 OP,没有,在 GCC 中没有办法告诉处理器总是假设分支被或者没有被占用。你所拥有的是内在的期望,它会做别人说它会做的事情。此外,我认为您不想告诉处理器是否采用了分支 一直都是。今天的处理器,比如 Intel 架构,可以识别相当复杂的模式并进行有效的适应。

但是,有时候您希望控制 默认情况下是否被预测采用: 当您知道代码在分支统计方面将被称为“ cold”时。

一个具体的例子: 异常管理代码。根据定义,管理代码将异常发生,但是当它发生时,可能需要获得最大的性能(可能有一个关键错误需要尽快处理) ,因此您可能希望控制默认预测。

另一个例子: 您可以对输入进行分类,然后跳转到处理分类结果的代码中。如果有许多分类,处理器可能会收集统计数据,但会丢失统计数据,因为同样的分类不会很快发生,而且预测资源被用于最近调用的代码。我希望有一个原语可以告诉处理器“请不要将预测资源投入到这段代码中”,就像您有时可以说“不要缓存这段代码”一样。

The correct way to define likely/unlikely macros in C++11 is the following:

#define LIKELY(condition) __builtin_expect(static_cast<bool>(condition), 1)
#define UNLIKELY(condition) __builtin_expect(static_cast<bool>(condition), 0)

[[likely]]不同,此方法与所有 C + + 版本兼容,但依赖于非标准扩展 __builtin_expect


当这些宏以这种方式定义时:

#define LIKELY(condition) __builtin_expect(!!(condition), 1)

这可能会改变 if语句的含义并破坏代码:

#include <iostream>


struct A
{
explicit operator bool() const { return true; }
operator int() const { return 0; }
};


#define LIKELY(condition) __builtin_expect((condition), 1)


int main() {
A a;
if(a)
std::cout << "if(a) is true\n";
if(LIKELY(a))
std::cout << "if(LIKELY(a)) is true\n";
else
std::cout << "if(LIKELY(a)) is false\n";
}

以及它的产出:

if(a) is true
if(LIKELY(a)) is false

如您所见,使用 !!作为对 bool的强制转换的 LIKELY 定义打破了 if的语义。

这里的重点不是 operator int()operator bool()应该是相关的,这是一个很好的实践。

相反,使用 !!(x)而不是 static_cast<bool>(x)会丢失 C++11 contextual conversions的上下文。

从 C + + 20开始,likely and unlikely attributes应该被标准化,并且已经支持 in g++9

if (a > b) {
/* code you expect to run often */
[[likely]] /* last statement here */
}

e.g. in the following code the else block gets inlined thanks to the [[unlikely]] in the if block

int oftendone( int a, int b );
int rarelydone( int a, int b );
int finaltrafo( int );


int divides( int number, int prime ) {
int almostreturnvalue;
if ( ( number % prime ) == 0 ) {
auto k                         = rarelydone( number, prime );
auto l                         = rarelydone( number, k );
[[unlikely]] almostreturnvalue = rarelydone( k, l );
} else {
auto a            = oftendone( number, prime );
almostreturnvalue = oftendone( a, a );
}
return finaltrafo( almostreturnvalue );
}

godbolt link comparing the presence/absence of the attribute