我一直在深入研究Linux内核的一些部分,发现了这样的调用:

if (unlikely(fd < 0))
{
    /* Do something */
}

or

if (likely(!err))
{
    /* Do something */
}

我找到了它们的定义:

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

我知道它们是为了优化,但它们是如何工作的呢?使用它们可以减少多少性能/大小?至少在瓶颈代码中(当然是在用户空间中),这样做是否值得(可能还会失去可移植性)。


它们是给编译器的提示,用于在分支上生成提示前缀。在x86/x64上,它们占用一个字节,因此每个分支最多增加一个字节。至于性能,它完全取决于应用程序——在大多数情况下,处理器上的分支预测器会忽略它们。

编辑:忘了一个他们能真正帮上忙的地方。它可以允许编译器重新排序控制流图,以减少“可能”路径的分支数量。在检查多个退出情况的循环中,这可以有显著的改进。


这些是GCC函数,供程序员向编译器提示给定表达式中最有可能出现的分支条件。这允许编译器构建分支指令,以便在最常见的情况下执行最少的指令。

如何构建分支指令取决于处理器架构。


它们是对编译器发出指令的提示,这些指令将导致分支预测倾向于跳转指令的“可能”一侧。这可能是一个巨大的胜利,如果预测是正确的,这意味着跳跃指令基本上是免费的,将采取零周期。另一方面,如果预测是错误的,那么这意味着处理器管道需要刷新,这可能会花费几个周期。只要预测在大多数时候是正确的,这将有利于性能。

就像所有这样的性能优化一样,你应该只在广泛的分析之后进行,以确保代码确实处于瓶颈中,并且可能考虑到微观性质,它正在一个紧密的循环中运行。一般来说,Linux开发人员都很有经验,所以我可以想象他们会这样做。他们不太关心可移植性,因为他们只针对gcc,而且他们对希望生成的程序集有非常接近的想法。


They cause the compiler to emit the appropriate branch hints where the hardware supports them. This usually just means twiddling a few bits in the instruction opcode, so code size will not change. The CPU will start fetching instructions from the predicted location, and flush the pipeline and start over if that turns out to be wrong when the branch is reached; in the case where the hint is correct, this will make the branch much faster - precisely how much faster will depend on the hardware; and how much this affects the performance of the code will depend on what proportion of the time hint is correct.

例如,在PowerPC CPU上,未提示的分支可能需要16个周期,正确提示的是8个周期,错误提示的是24个周期。在最内层循环中,良好的暗示可以产生巨大的差异。

可移植性并不是真正的问题——假设定义是在每个平台的头文件中;对于不支持静态分支提示的平台,可以简单地将“可能”和“不太可能”定义为零。


这些宏向编译器提供了关于分支可能走向的提示。如果这些宏可用,它们将扩展为特定于GCC的扩展。

GCC使用这些来优化分支预测。例如,如果您有以下内容

if (unlikely(x)) {
  dosomething();
}

return x;

然后它可以重新构造这段代码,使其更像:

if (!x) {
  return x;
}

dosomething();
return x;

这样做的好处是,当处理器第一次使用分支时,会有很大的开销,因为它可能已经预先加载和执行了代码。当它决定使用分支时,它必须使其无效,并从分支目标开始。

大多数现代处理器现在都有某种分支预测,但这只在您之前已经通过了分支,并且分支仍然在分支预测缓存中时才有帮助。

在这些场景中,编译器和处理器还可以使用许多其他策略。你可以在Wikipedia上找到关于分支预测器如何工作的更多细节:http://en.wikipedia.org/wiki/Branch_predictor


(一般评论-其他答案涵盖了细节)

没有理由因为使用它们而失去可移植性。

你总是可以选择创建一个简单的无效果“内联”或宏,这将允许你在其他平台上使用其他编译器进行编译。

如果您在其他平台上,则无法获得优化的好处。


在许多linux版本中,你可以在/usr/linux/中找到compiler.h,你可以简单地把它包含进来。另一种观点是,unlikely()比likely()更有用,因为

if ( likely( ... ) ) {
     doSomething();
}

它也可以在许多编译器中进行优化。

顺便说一下,如果你想观察代码的详细行为,你可以简单地做如下所示:

测试 Objdump -d测试。O > obj.s

然后,打开obj。S,你可以找到答案。


根据Cody的评论,这与Linux无关,但这是对编译器的一个提示。会发生什么取决于体系结构和编译器版本。

Linux中的这个特殊特性在驱动程序中被误用了。正如osgx在hot属性语义中指出的那样,在一个块中调用的任何热函数或冷函数都可以自动提示该条件是否可能。例如,dump_stack()被标记为冷,所以这是多余的,

 if(unlikely(err)) {
     printk("Driver error found. %d\n", err);
     dump_stack();
 }

gcc的未来版本可能会根据这些提示选择性地内联一个函数。也有建议说,它不是布尔值,而是一个分数,就像在最有可能,等等。一般来说,应该优先使用一些替代机制,如冷。没有理由在炎热的道路以外的任何地方使用它。编译器在一种架构上的功能在另一种架构上可能完全不同。


让我们反编译,看看GCC 4.8对它做了什么

没有__builtin_expect

#include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        printf("%d\n", i);
    puts("a");
    return 0;
}

使用GCC 4.8.2 x86_64 Linux编译和反编译:

gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o

输出:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       75 14                   jne    24 <main+0x24>
  10:       ba 01 00 00 00          mov    $0x1,%edx
  15:       be 00 00 00 00          mov    $0x0,%esi
                    16: R_X86_64_32 .rodata.str1.1
  1a:       bf 01 00 00 00          mov    $0x1,%edi
  1f:       e8 00 00 00 00          callq  24 <main+0x24>
                    20: R_X86_64_PC32       __printf_chk-0x4
  24:       bf 00 00 00 00          mov    $0x0,%edi
                    25: R_X86_64_32 .rodata.str1.1+0x4
  29:       e8 00 00 00 00          callq  2e <main+0x2e>
                    2a: R_X86_64_PC32       puts-0x4
  2e:       31 c0                   xor    %eax,%eax
  30:       48 83 c4 08             add    $0x8,%rsp
  34:       c3                      retq

内存中的指令顺序没有改变:首先是printf,然后是put,然后是retq返回。

与__builtin_expect

现在将if (i)替换为:

if (__builtin_expect(i, 0))

我们得到:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       74 11                   je     21 <main+0x21>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1+0x4
  15:       e8 00 00 00 00          callq  1a <main+0x1a>
                    16: R_X86_64_PC32       puts-0x4
  1a:       31 c0                   xor    %eax,%eax
  1c:       48 83 c4 08             add    $0x8,%rsp
  20:       c3                      retq
  21:       ba 01 00 00 00          mov    $0x1,%edx
  26:       be 00 00 00 00          mov    $0x0,%esi
                    27: R_X86_64_32 .rodata.str1.1
  2b:       bf 01 00 00 00          mov    $0x1,%edi
  30:       e8 00 00 00 00          callq  35 <main+0x35>
                    31: R_X86_64_PC32       __printf_chk-0x4
  35:       eb d9                   jmp    10 <main+0x10>

printf(编译为__printf_chk)被移动到函数的最末端,在put和return之后,以改进其他答案中提到的分支预测。

所以基本上是一样的:

int main() {
    int i = !time(NULL);
    if (i)
        goto printf;
puts:
    puts("a");
    return 0;
printf:
    printf("%d\n", i);
    goto puts;
}

这个优化不是用-O0完成的。

但是希望你能写出一个使用__builtin_expect比不使用__builtin_expect运行得更快的例子,现在的cpu真的很聪明。我天真的尝试在这里。

c++ 20[[可能]]]和[[不可能]]

c++ 20标准化了那些c++内置:如何在if-else语句中使用c++ 20的likely/unlikely属性,它们可能(双关语!)做同样的事情。


long __builtin_expect(long EXP, long C);

这个构造告诉编译器表达式EXP . xml 最有可能的值是c。返回值是EXP。 __builtin_expect用于条件句 表达式。在几乎所有的情况下,它将被用于 上下文的布尔表达式在这种情况下,它是很多 定义两个helper宏更方便:

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

这些宏可以用于

if (likely(a > 1))

参考:https://www.akkadia.org/drepper/cpumemory.pdf