Linux内核中可能/不可能的宏是如何工作的,它们的好处是什么?

Linux内核中可能/不可能的宏是如何工作的,它们的好处是什么?,linux,gcc,linux-kernel,likely-unlikely,Linux,Gcc,Linux Kernel,Likely Unlikely,我一直在深入研究Linux内核的某些部分,发现了如下调用: if (unlikely(fd < 0)) { /* Do something */ } 我找到了它们的定义: #define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) 我知道它们是用于优化的,但是它们是如何工作的呢?使用它们可以减少多少性能/大小?至少在瓶颈代码(当然是在用户空间中)中

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

if (unlikely(fd < 0))
{
    /* Do something */
}
我找到了它们的定义:

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

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

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


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

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


分支指令的生成方式取决于处理器体系结构。

它们提示编译器发出指令,使分支预测有利于跳转指令的“可能”端。这可能是一个巨大的胜利,如果预测是正确的,这意味着跳转指令基本上是免费的,将需要零个周期。另一方面,如果预测是错误的,则意味着需要刷新处理器管道,这可能需要花费几个周期。只要预测在大多数情况下是正确的,这将有助于提高性能


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

他们使编译器在硬件支持的地方发出适当的分支提示。这通常只意味着在指令操作码中旋转几位,所以代码大小不会改变。CPU将开始从预测的位置获取指令,并刷新管道,如果到达分支时发现错误,则重新开始;在提示正确的情况下,这将使分支更快-确切地说,速度将取决于硬件;这对代码性能的影响程度将取决于提示正确的时间比例

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


可移植性并不是一个真正的问题——大概定义在每个平台的标题中;对于不支持静态分支提示的平台,您可以简单地将“可能”和“不可能”定义为“无”。

这些宏向编译器提示分支的走向。宏将扩展为特定于GCC的扩展(如果可用)

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

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

return x;
然后它可以将此代码重新构造为类似于:

if (!x) {
  return x;
}

dosomething();
return x;
这样做的好处是,当处理器第一次执行一个分支时,会有很大的开销,因为它可能是在推测性地加载和执行代码。当它确定它将接受分支时,它必须使其无效,并从分支目标开始

大多数现代处理器现在都有某种分支预测功能,但这只在您以前经历过分支并且分支仍在分支预测缓存中时才有帮助

在这些场景中,编译器和处理器还可以使用许多其他策略。您可以在Wikipedia上找到分支预测器工作原理的更多详细信息:

(一般性评论-其他答案包括详细信息)

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

您始终可以选择创建一个简单的无效果“内联”或宏,以允许您使用其他编译器在其他平台上编译


如果您在其他平台上,您将无法从优化中获益。

在许多linux版本中,您可以在/usr/linux/中找到complier.h,您可以简单地将其包括在内。另一种观点是,不太可能()比可能()更有用,因为

if ( likely( ... ) ) {
     doSomething();
}
它也可以在许多编译器中进行优化

顺便说一下,如果您想观察代码的详细行为,您可以简单地执行以下操作:

gcc-c测试 objdump-d test.o>obj.s

然后,打开obj.s,您可以找到答案。

根据注释,这与Linux无关,但对编译器来说是一个提示。发生的情况将取决于体系结构和编译器版本

Linux中的这一特殊功能在驱动程序中使用不当。正如中所指出的,在块中调用的任何
hot
cold
函数都可以自动提示该情况是否可能。例如,
dump\u stack()

 if(unlikely(err)) {
     printk("Driver error found. %d\n", err);
     dump_stack();
 }
未来版本的
gcc
可能会基于这些提示选择性地内联函数。也有人认为这不是布尔值,而是最有可能的分数,等等
 if(unlikely(err)) {
     printk("Driver error found. %d\n", err);
     dump_stack();
 }
#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 -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
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>
int main() {
    int i = !time(NULL);
    if (i)
        goto printf;
puts:
    puts("a");
    return 0;
printf:
    printf("%d\n", i);
    goto puts;
}
long __builtin_expect(long EXP, long C);
#define unlikely(expr) __builtin_expect(!!(expr), 0)
#define likely(expr) __builtin_expect(!!(expr), 1)
if (likely(a > 1))