Is there a compiler hint for GCC to force branch prediction to always go a certain way?
对于英特尔体系结构,有没有一种方法可以指示GCC编译器生成代码,在我的代码中总是强制使用分支预测的特定方法?Intel硬件是否支持此功能?其他编译器或硬件呢?
我会在C++代码中使用这个代码,在这里我知道我希望快速运行的情况,也不关心其他分支需要采取的速度慢,即使它最近已经占领了该分支。
1 2 3 4 5 6 7 | for (;;) { if (normal) { // How to tell compiler to always branch predict true value? doSomethingNormal(); } else { exceptionalCase(); } } |
作为evdzhan mustafa的后续问题,提示能否仅指定处理器第一次遇到指令时的提示,所有后续的分支预测,正常工作?
gcc支持函数
其中,
1 | if (__builtin_expect(normal, 1)) |
由于语法不好,通常通过定义两个自定义宏来使用
1 2 | #define likely(x) __builtin_expect (!!(x), 1) #define unlikely(x) __builtin_expect (!!(x), 0) |
只是为了减轻任务。
介意:
GCC有long_uu builtin_expect(long exp,long c)(emphasis mine):
You may use __builtin_expect to provide the compiler with branch
prediction information. In general, you should prefer to use actual
profile feedback for this (-fprofile-arcs), as programmers are
notoriously bad at predicting how their programs actually perform.
However, there are applications in which this data is hard to collect.The return value is the value of exp, which should be an integral
expression. The semantics of the built-in are that it is expected that
exp == c. For example:
1
2 if (__builtin_expect (x, 0))
foo ();indicates that we do not expect to call foo, since we expect x to be
zero. Since you are limited to integral expressions for exp, you
should use constructions such as
1
2 if (__builtin_expect (ptr != NULL, 1))
foo (*ptr);when testing pointer or floating-point values.
正如文档所指出的,您应该更喜欢使用实际的概要文件反馈,本文给出了一个实际的例子,以及在这种情况下,它是如何比使用
我们还可以找到一篇关于kernal macros likely()和unsible()的Linux内核新手文章,其中使用了以下功能:
1 2 | #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) |
注意宏中使用的
仅仅因为这种技术在Linux内核中使用并不意味着使用它总是有意义的。我们可以从这个问题看出,我最近回答了将参数作为编译时常量或变量传递时函数性能之间的差异,许多手动滚动优化技术在一般情况下不起作用。我们需要仔细分析代码以了解一种技术是否有效。许多旧技术甚至可能与现代编译器优化无关。
请注意,虽然内置设备不是可移植的,但是clang也支持内置设备expect。
另外,在某些架构上,它可能不会有什么不同。
不,没有。(至少在现代x86处理器上是如此。)
其他答案中提到的
有关详细信息,请参阅此问题:实际使用的Intel x86 0x2e/0x3e前缀分支预测?
明确地说,
在C++ 11中定义可能的/不太可能的宏的正确方法是:
1 2 | #define LIKELY(condition) __builtin_expect(static_cast<bool>(condition), 1) #define UNLIKELY(condition) __builtin_expect(static_cast<bool>(condition), 0) |
当这些宏以这种方式定义时:
1 | #define LIKELY(condition) __builtin_expect(!!(condition), 1) |
这可能会改变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #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 "; if(LIKELY(a)) std::cout <<"if(LIKELY(a)) is true "; else std::cout <<"if(LIKELY(a)) is false "; } |
其输出:
1 2 | if(a) is true if(LIKELY(a)) is false |
如您所见,可能使用
这里的重点不是
相反,使用EDCOX1〔11〕代替EDCOX1〔12〕,就失去了C++ 11上下文转换的上下文。
由于其他答案都有充分的建议,您可以使用
沿着类似的行(但还没有提到),是一种强制编译器在"冷"路径上生成代码的GCC特定方法。这涉及到
虽然这仍然属于微观优化的一般范畴,因此标准建议应用test-don-guess-我觉得它比
样品使用情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 ? } |
更好的是,当配置文件反馈可用时(例如,在使用
参见官方文档:https://gcc.gnu.org/onlinedocs/gcc/common-function-attributes.html common-function-attributes
_ builtin-expect可以用来告诉编译器一个分支应该朝哪个方向发展。这会影响代码的生成方式。典型的处理器按顺序运行代码更快。所以如果你写
1 2 3 | if (__builtin_expect (x == 0, 0)) ++count; if (__builtin_expect (y == 0, 0)) ++count; if (__builtin_expect (z == 0, 0)) ++count; |
编译器将生成类似
1 2 3 4 5 6 7 8 | 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处理器对预期要执行的分支或预期不执行的分支具有指令(有一个指令前缀;不确定详细信息)。不确定处理器是否使用它。它不是很有用,因为分支预测可以很好地处理这个问题。所以我认为你不能真正影响分支预测。
关于操作,不,在GCC中没有办法告诉处理器总是假设分支被占用或不被占用。你所拥有的是内在的期待,它会像别人所说的那样。此外,我认为您不想告诉处理器是否总是使用分支。今天的处理器,如英特尔体系结构,可以识别相当复杂的模式并有效地适应。
但是,有时您希望控制默认情况下是否使用分支:当您知道分支统计信息的代码将被称为"cold"时。
一个具体的例子:异常管理代码。根据定义,管理代码将异常发生,但可能在发生时需要最大的性能(可能有一个关键错误需要尽快处理),因此您可能希望控制默认预测。
另一个例子:您可以对输入进行分类,并跳转到处理分类结果的代码中。如果有许多分类,处理器可能会收集统计数据,但会丢失它们,因为相同的分类不会很快发生,并且预测资源专门用于最近调用的代码。我希望有一个原语告诉处理器"请不要将预测资源投入到这段代码中",就像你有时说的"不要缓存这段代码"。