likely(x)与unlikely(x)函数,即__builtin_expect的使用

likely(x)与unlikely(x)函数,即__builtin_expect的使用

转载自:http://velep.com/archives/795.html

 

本文讲的likely()和unlikely()两个宏,在linux内核代码和一些应用中可常见到它们的身影。实质上,这两个宏是关于GCC编译器内置宏__builtin_expect的使用。

顾名思义,likely()指“很有可能”之意,而unlikely()指“不太可能”之意。那么,在实际应用中,它们代表什么?又是怎么使用的呢?下面是一篇外文翻译(加上了本人的一些理解),给出了详细答案。

likely()和unlikely()

对于linux内核代码,在条件判断语句中经常看到likely()和unlikely()的调用,如下代码所示:

复制代码
bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx);
if (unlikely(!bvl)) {
  mempool_free(bio, bio_pool);
  bio = NULL;
  goto out;
}
复制代码


在这里,调用likely()或unlikely()告诉编译器这个条件很有可能或者不太有可能发生,好让编译器对这个条件判断进行正确地优化。这两个宏在include/linux/compiler.h文件中可以找到:

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


在GCC文档中可找到上述代码中__builtin_expect的说明,摘录如下:

复制代码
    -- Built-in Function: long __builtin_expect (long EXP, long C)
        You may use `__builtin_expect' to provide the compiler with branch
        prediction information.  In general, you should prefer to use
        actual profile feedback for this (`-fprofile-arcs'), as
        programmers are notoriously bad at predicting how their programs
        actually perform.  However, there are applications in which this
        data is hard to collect.
    The </span><span style="color: #0000ff;">return</span> value <span style="color: #0000ff;">is</span><span style="color: #000000;"> the value of EXP, which should be an integral
    expression.  The value of C must be a compile</span>-<span style="color: #000000;">time constant.  The
    semantics of the built</span>-<span style="color: #0000ff;">in</span> are that it <span style="color: #0000ff;">is</span> expected that EXP ==<span style="color: #000000;"> C.
    For example:
 
         </span><span style="color: #0000ff;">if</span> (__builtin_expect (x, <span style="color: #800080;">0</span><span style="color: #000000;">))
           foo ();
 
    would indicate that we </span><span style="color: #0000ff;">do</span> not expect to call `foo<span style="color: #800000;">'</span><span style="color: #800000;">, since we</span>
    expect `x<span style="color: #800000;">'</span><span style="color: #800000;"> to be zero.  Since you are limited to integral</span>
    expressions <span style="color: #0000ff;">for</span> EXP, you should use constructions such <span style="color: #0000ff;">as</span>
 
         <span style="color: #0000ff;">if</span> (__builtin_expect (ptr != NULL, <span style="color: #800080;">1</span><span style="color: #000000;">))
           error ();
 
    when testing pointer or floating</span>-point values.</span></pre>
复制代码


__builtin_expect说明中给出了两示例:

if (__builtin_expect (x, 0)) foo (); 表示期望x == 0,也就是不期望不执行foo()函数;同理,if (__builtin_expect (ptr != NULL, 1)) error (); 表示期望指针prt非空,也就是不期望看到error()函数的执行。

编译器做的优化工作

从GCC的说明中可知,__builtin_expect的主要作用就是:帮助编译器判断条件跳转的预期值,避免因执行jmp跳转指令造成时间浪费。那么它是怎么帮助编译器进行优化的呢?

编译器优化时,根据条件跳转的预期值,按正确地顺序生成汇编代码,把“很有可能发生”的条件分支放在顺序执行指令段,而不是jmp指令段(jmp指令会打乱CPU的指令执行顺序,大大影响CPU指令执行效率)。

下面举例说明。下面这个简单的C程序使用gcc -O2进行编译。

复制代码
#define likely(x)    __builtin_expect(!!(x), 1)
#define unlikely(x)  __builtin_expect(!!(x), 0)

int main(char *argv[], int argc)
{
int a;

/ 获取输入参数值(编译器不能进行优化) /
a = atoi (argv[1]);

if (unlikely (a == 2))
a++;
else
a–;

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

return 0;
}

复制代码


使用objdump -S反汇编,查看它的汇编代码。

复制代码
80483b0 <main>:
//             开头
80483b0:       55                      push   %ebp
80483b1:       89 e5                   mov    %esp,%ebp
80483b3:       50                      push   %eax
80483b4:       50                      push   %eax
80483b5:       83 e4 f0                and    $0xfffffff0,%esp
//             调用atoi()
80483b8:       8b 45 08                mov    0x8(%ebp),%eax
80483bb:       83 ec 1c                sub    $0x1c,%esp
80483be:       8b 48 04                mov    0x4(%eax),%ecx
80483c1:       51                      push   %ecx
80483c2:       e8 1d ff ff ff          call   80482e4 <atoi@plt>
80483c7:       83 c4 10                add    $0x10,%esp
//             把输入值与2进行比较,即执行:“a == 2”
80483ca:       83 f8 02                cmp    $0x2,%eax
//             --------------------------------------------------------
//             如果'a' 等于 2 (程序里面认为不太可能), 则跳转,
//             否则继续执行, 从而不破坏CPU的指令执行顺序.
//             --------------------------------------------------------
80483cd:       74 12                   je     80483e1 <main+0x31>
80483cf:       48                      dec    %eax
//             调用printf
80483d0:       52                      push   %edx
80483d1:       52                      push   %edx
80483d2:       50                      push   %eax
80483d3:       68 c8 84 04 08          push   $0x80484c8
80483d8:       e8 f7 fe ff ff          call   80482d4 <printf@plt>
//             返回0并退出.
80483dd:       31 c0                   xor    %eax,%eax
80483df:       c9                      leave
80483e0:       c3                      ret
复制代码


在上面程序中,用likely()代替其中的unlikely(),重新编译,再来看它的汇编代码:

复制代码
80483b0 <main>:
//             开头
80483b0:       55                      push   %ebp
80483b1:       89 e5                   mov    %esp,%ebp
80483b3:       50                      push   %eax
80483b4:       50                      push   %eax
80483b5:       83 e4 f0                and    $0xfffffff0,%esp
//             调用atoi()
80483b8:       8b 45 08                mov    0x8(%ebp),%eax
80483bb:       83 ec 1c                sub    $0x1c,%esp
80483be:       8b 48 04                mov    0x4(%eax),%ecx
80483c1:       51                      push   %ecx
80483c2:       e8 1d ff ff ff          call   80482e4 <atoi@plt>
80483c7:       83 c4 10                add    $0x10,%esp
//             --------------------------------------------------
//             如果'a' 等于 2 (程序认为很有可能), 则不跳转,继续执行,
//             这样就不破坏CPU的指令执行顺序.
//             只有当 a != 2 时才会发生跳转, 而这种情况,程序认为是不太可能的.
//             ---------------------------------------------------
80483ca:       83 f8 02                cmp    $0x2,%eax
80483cd:       75 13                   jne    80483e2 <main+0x32>
//             a++ 指令的优化
80483cf:       b0 03                   mov    $0x3,%al
//             调用printf()
80483d1:       52                      push   %edx
80483d2:       52                      push   %edx
80483d3:       50                      push   %eax
80483d4:       68 c8 84 04 08          push   $0x80484c8
80483d9:       e8 f6 fe ff ff          call   80482d4 <printf@plt>
//             返回0并退出.
80483de:       31 c0                   xor    %eax,%eax
80483e0:       c9                      leave
80483e1:       c3                      ret
复制代码


如何使用?

在一个条件判断语句中,当这个条件被认为是非常非常有可能满足时,则使用likely()宏,否则,条件非常非常不可能或很难满足时,则使用unlikely()宏。

参考资料

本文英文原文:http://kernelnewbies.org/FAQ/LikelyUnlikely

更多GCC内置宏或函数,详见:http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页