为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

莫回无 2019-07-13 19:02:43
我一直在深入研究Linux内核的某些部分,并发现了这样的调用:if (unlikely(fd < 0)){     /* Do something */}或if (likely(!err)){     /* Do something */}我找到了他们的定义:#define likely(x)       __builtin_expect((x),1)#define unlikely(x)     __builtin_expect((x),0)我知道它们是用于优化的,但是它们是如何工作的呢?使用它们可以减少多少性能/大小?至少在瓶颈代码中(当然,在用户空间中),这是否值得麻烦(并且可能会失去可移植性)。
查看完整描述

3 回答

?
呼唤远方

TA贡献1856条经验 获得超11个赞

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

像所有这样的性能优化一样,您只应该在进行了广泛的分析之后才会这样做,以确保代码确实处于瓶颈状态,并且可能考虑到它的微观特性,它是在一个紧密的循环中运行的。一般来说,Linux开发人员都很有经验,所以我可以想象他们会这么做。他们并不太关心可移植性,因为他们只关注GCC,而且他们对他们希望它产生的组装有一个非常密切的概念。


查看完整回答
反对 回复 2019-07-13
?
GCT1015

TA贡献1827条经验 获得超4个赞

让我们来看看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.2x86_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然后puts而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)被移到函数的末尾,之后puts以及其他答案中提到的改进分支预测的回报。


所以基本上是一样的:


int i = !time(NULL);

if (i)

    goto printf;

puts:

puts("a");

return 0;

printf:

printf("%d\n", i);

goto puts;

这个优化没有用-O0.


但是,在编写一个运行速度更快的示例时,祝您好运。__builtin_expect比没有,那些时候CPU真的很聪明..我天真的尝试在这里.


查看完整回答
反对 回复 2019-07-13
  • 3 回答
  • 0 关注
  • 571 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信