我一直在挖掘Linux内核的某些部分,发现这样的调用:
if (unlikely(fd < 0))
{
/* Do something */
}
Run Code Online (Sandbox Code Playgroud)
要么
if (likely(!err))
{
/* Do something */
}
Run Code Online (Sandbox Code Playgroud)
我找到了它们的定义:
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
Run Code Online (Sandbox Code Playgroud)
我知道它们是为了优化,但它们是如何工作的?使用它们可以预期性能/尺寸减少多少?至少在瓶颈代码中(当然在用户空间中)是否值得麻烦(并且可能失去可移植性).
为了说清楚,我不打算在这里使用任何类型的便携性,所以任何将我绑定到某个盒子的解决方案都可以.
基本上,我有一个if语句将99%的时间评估为true,并且我试图剔除每个性能的最后一个时钟,我可以发出某种编译器命令(使用GCC 4.1.2和x86 ISA,如果告诉分支预测器它应该缓存该分支吗?
这个问题是关于C++ 20的[[likely]]/ [[unlikely]]功能,而不是编译器定义的宏.
这个文档(cppreference)只给出了一个将它们应用于switch-case语句的例子.这个switch-case示例与我的编译器完美编译(g ++ - 7.2),所以我假设编译器已经实现了这个功能,尽管它还没有在当前的C++标准中正式引入.
但是当我像这样使用它们时if (condition) [[likely]] { ... } else { ... },我收到了一个警告:
"警告:语句开头的属性被忽略[-Wattributes]".
那么我应该如何在if-else语句中使用这些属性呢?
我理解这里解释的内容以及这些内容包括CPU对静态分支预测的提示.
我想知道英特尔CPU上的这些是如何相关的,因为英特尔CPU已经放弃了对这里提到的静态预测提示的支持.此外,如果我理解它现在如何工作,路径中的分支指令的数量将是编译器可以控制的唯一事物,并且在运行时决定预测,获取和解码哪个分支路径.
鉴于此,是否存在代码中的分支提示对于针对最近的英特尔处理器的软件仍然有用的情况,可能使用条件返回或者在嵌套的if/else语句的情况下避免关键路径中的分支指令数量?
此外,如果这些仍然相关,那么gcc和其他流行编译器的任何细节都会受到赞赏.
PS我不是为了过早优化或者用这些宏来编写代码,但是我对这个主题很感兴趣,因为我正在使用一些时间关键代码,并且仍然希望尽可能减少代码混乱.
谢谢
请考虑以下代码:
void error_handling();
bool method_impl();
bool method()
{
const bool res = method_impl();
if (res == false) {
error_handling();
return false;
}
return true;
}
Run Code Online (Sandbox Code Playgroud)
我知道当时method_impl()会返回true99.999%(是的,三位小数),但我的编译器没有.method()在时间消耗方面是部分关键.
method()(并使其不太可读)以确保只有在method_impl()返回时才会发生跳转false?如果有,怎么样?我发现了这个非常漂亮的信息图,它对某些操作所使用的 CPU 周期进行了粗略估计。在学习时,我注意到一个条目“if的右分支”,我认为如果满足条件,该分支将采用“if”(编辑:正如评论中指出的“右”实际上意味着“正确预测的分支” )。这让我想知道 if 分支与 else 分支相比是否存在任何(即使是很小的)速度差异。
例如,比较以下非常简洁的代码:
#include <cstdio>
volatile int a = 2;
int main()
{
if (a > 5) {
printf("a > 5!");
a = 2;
} else {
printf("a <= 5!");
a = 3;
}
}
Run Code Online (Sandbox Code Playgroud)
它在 x86 64 位中生成此程序集:
.LC0:
.string "a > 5!"
.LC1:
.string "a <= 5!"
main:
push rcx
mov eax, DWORD PTR a[rip]
cmp eax, 5
jle .L2
mov edi, OFFSET FLAT:.LC0
xor eax, eax …Run Code Online (Sandbox Code Playgroud) 我在代码中有很多验证检查,如果任何检查失败,程序将崩溃.因此所有检查都不太可能.
if( (msg = newMsg()) == (void *)0 )//this is more unlikely
{
panic()//crash
}
Run Code Online (Sandbox Code Playgroud)
所以我使用了不太可能在分支预测中提示编译器的宏.但是我没有看到这方面的改进(我有一些性能测试).我正在使用gcc4.6.3.
为什么没有改善?是因为没有其他情况吗?我应该在构建应用程序时使用任何优化标志吗?
我从Microsoft的GSL实现(C++指南支持库)中看到了这段代码:
#if defined(__clang__) || defined(__GNUC__)
#define GSL_LIKELY(x) __builtin_expect(!!(x), 1)
#define GSL_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define GSL_LIKELY(x) (!!(x))
#define GSL_UNLIKELY(x) (!!(x))
#endif
Run Code Online (Sandbox Code Playgroud)
我读到了__builtin_expect(这里和这里),但我仍然不清楚双boolean否定运算符的目的是什么(!!(x)).为什么用它?
有问题的文件就是这个.
我不是在问类似的事情__builtin_expect。我在考虑一种情况,即我不知道分支通常是对还是错,但是我确实知道分支是可预测的(或不是可预测的)。
我希望编译器在知道分支是可预测的之后,更有可能生成分支,并且知道它是不可预测的,因此更有可能在没有分支的情况下生成有条件执行的指令。
在主要编译器中可能吗?(专门考虑gcc和clang)。
解释“可预测”和“可能”为何不同的示例
int x = rand()%2;
while (true) {
if (x) {
// do something
}
}
Run Code Online (Sandbox Code Playgroud)
该if声明既不太可能也不太可能,但可以高度预测。
while (true) {
if (rand()%5 > 0) {
// do something
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,情况恰恰相反:分支很有可能(占80%的时间),但不可预测。
c++ ×6
c ×5
gcc ×4
assembly ×2
performance ×2
x86 ×2
boost ×1
c++20 ×1
gcc-warning ×1
intel ×1
linux ×1
linux-kernel ×1
llvm-clang ×1
macros ×1
negation ×1
optimization ×1