GCC是否有编译提示强制分支预测总是以某种方式?

对于Intel体系结构,是否有办法指示GCC编译器生成代码,这些代码始终会在代码中以特定方式强制执行分支预测? 英特尔硬件是否支持这个? 其他编译器或硬件呢?

我会在C ++代码中使用它,我知道我希望快速运行的情况下,而不必关心其他分支即使最近采取该分支时需要采取的减速。

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

作为Evdzhan Mustafa的后续问题,提示只是第一次指出处理器遇到该指令时的提示,所有后续的分支预测,function是否正常?

在C ++ 11中定义可能/不太可能的macros的正确方法如下:

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

当这些macros定义如下:

 #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 

正如你所看到的,LIKELY的定义使用!! 作为bool打破了语义的if

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

而是使用!!(x)而不是static_cast<bool>(x)丢失了C ++ 11上下文转换的上下文 。

GCC支持函数__builtin_expect(long exp, long c)来提供这种function。 你可以在这里查看文档。

exp是使用的条件, c是预期值。 例如在你的情况下,你会想

 if (__builtin_expect(normal, 1)) 

由于语法尴尬,通常通过定义两个自定义macros来使用

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

只是为了缓解任务。

注意:

  1. 这是不标准的
  2. 编译器/ CPU分支预测器可能比你更擅长决定这样的事情,所以这可能是一个过早的微观优化

海湾合作委员会有很长的__builtin_expect(长期的exp,长期的c) ( 强调我的 ):

您可以使用__builtin_expect为编译器提供分支预测信息。 一般来说, 您应该更喜欢使用实际的configuration文件反馈(-fprofile-arcs),因为程序员在预测其程序的实际执行方式方面出了名的糟糕 。 但是,有些应用程序难以收集这些数据。

返回值是exp的值,它应该是一个整数expression式。 内置的语义是,预计exp == c。 例如:

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

表明我们不期望称foo,因为我们期望x为零。 由于您仅限于exp的整数expression式,所以您应该使用如。的结构

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

当testing指针或浮点值时。

作为文档说明,您应该更喜欢使用实际的configuration文件反馈, 本文展示了一个实际的例子,以及如何在他们的情况下,至less最终是使用__builtin_expect改进。 另请参阅如何在g ++中使用概要指导优化? 。

我们也可以find一个关于内核macros的Linux内核新手文章,可能()和不太可能()使用这个特性:

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

注意!! 在macros中使用我们可以find解释为什么使用!!(条件),而不是(条件)? 。

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

注意,尽pipebuiltins不是可移植的,但clang也支持__builtin_expect 。

在一些架构上也可能没有什么区别 。

不,那里没有。 (至less在现代的x86处理器上)

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

看到这个问题的更多细节: 英特尔x86 0x2E / 0x3E前缀分支预测实际使用?

清楚的是, -fprofile-arcs和/或-fprofile-arcs的使用可以提高代码的性能,通过代码布局给分支预测器提供提示(请参阅x86-64汇编的性能优化 – alignment和分支预测 ),并且通过使“不太可能”的代码远离“可能的”代码来改善caching行为。

由于其他的答案已经被充分的build议,你可以使用__builtin_expect来给编译器一个关于如何排列汇编代码的提示。 正如官方文件指出的那样,在大多数情况下,内置在你脑海中的汇编程序将不如GCC团队精心制作的汇编程序。 最好使用实际的configuration文件数据来优化您的代码,而不是猜测。

沿着类似的路线,但尚未提到,是GCC特定的方式来强制编译器在“冷”path上生成代码。 这涉及到使用noinlinecold属性,这些属性的确如他们所做的那样。 这些属性只能应用于函数,但在C ++ 11中,可以声明内联lambda函数,这两个属性也可以应用于lambda函数。

虽然这仍然属于微优化的一般类别,因此标准的build议应用testing不要猜测 – 我觉得它比__builtin_expect更普遍有用。 几乎任何一代x86处理器都使用分支预测提示( reference ),所以无论如何,唯一能够影响的是汇编代码的顺序。 由于您知道什么是error handling或“边缘情况”代码,因此您可以使用此注释来确保编译器不会预测到某个分支,并在优化大小时将其从“热门”代码中链接出来。

示例用法:

 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 ⋮ } 

更好的是,当GCC可用时(例如,使用-fprofile-use编译时),GCC会自动忽略这一点。

请参阅官方文档: https : //gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

__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处理器有预期将被采用的分支或预期不被采用的分支的指令(有一个指令前缀;不清楚细节)。 不知道处理器是否使用了。 这不是很有用,因为分支预测会处理这个很好。 所以我不认为你实际上可以影响分支预测

关于OP,不,GCC没有办法告诉处理器总是假设分支是否被占用。 你有什么是__builtin_expect,这就是别人所说的。 此外,我认为你不想告诉处理器分支是否被采取或不总是 。 今天的处理器,如英特尔架构,可以识别相当复杂的模式,并有效地适应。

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

一个具体的例子:例外pipe理代码。 根据定义,pipe理代码将会exception地发生,但是也许发生时最大的性能是可能的(可能会有一个严重的错误需要尽快处理),因此您可能需要控制默认的预测。

另一个例子:您可以对input进行分类并跳转到处理分类结果的代码中。 如果存在多个分类,处理器可能会收集统计数据但丢失数据,因为相同的分类不会很快发生,并且预测资源专用于最近被调用的代码。 我希望有一个原始的方式告诉处理器,“请不要把预测资源投入到这个代码”,你有时可以说“不要caching这个”的方式。