赞
踩
目录
在很多源码如Linux内核、Glib等,我们都能看到likely()和unlikely()这两个宏,通常这两个宏定义是下面这样的形式。
- #define likely(x) __builtin_expect(!!(x), 1)
- #define unlikely(x) __builtin_expect(!!(x), 0)
可以看出这2个宏都是使用函数 __builtin_expect()实现的, __builtin_expect()函数是GCC的一个内建函数(build-in function).
函数__builtin_expect()是GCC v2.96版本引入的, 其声明如下:
long __builtin_expect(long exp, long c);
由于大部分程序员在分支预测方面做得很糟糕,所以GCC 提供了这个内建函数来帮助程序员处理分支预测.
你期望 exp 表达式的值等于常量 c, 看 c 的值, 如果 c 的值为0(即期望的函数返回值), 那么 执行 if 分支的的可能性小, 否则执行 else 分支的可能性小(函数的返回值等于第一个参数 exp).
GCC在编译过程中,会将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降, 达到优化程序的目的.
通常,你也许会更喜欢使用 gcc 的一个参数 '-fprofile-arcs' 来收集程序运行的关于执行流程和分支走向的实际反馈信息,但是对于很多程序来说,数据是很难收集的。
exp 为一个整型表达式, 例如: (ptr != NULL)
c 必须是一个编译期常量, 不能使用变量
返回值等于 第一个参数 exp
与关键字if一起使用.首先要明确一点就是 if (value) 等价于 if (__builtin_expert(value, x)), 与x的值无关.
例子如下:
例子1 : 期望 x == 0, 所以执行func()的可能性小
- if (__builtin_expect(x, 0))
- {
- func();
- }
- else
- {
- //do someting
- }
例子2 : 期望 ptr !=NULL这个条件成立(1), 所以执行func()的可能性小
- if (__builtin_expect(ptr != NULL, 1))
- {
- //do something
- }
- else
- {
- func();
- }
例子3 : 引言中的likely()和unlikely()宏
首先,看第一个参数!!(x), 他的作用是把(x)转变成"布尔值", 无论(x)的值是多少 !(x)得到的是true或false, !!(x)就得到了原值的"布尔值"
使用 likely() ,执行 if 后面的语句 的机会更大,使用 unlikely(),执行 else 后面的语句的机会更大。
- #define likely(x) __builtin_expect(!!(x), 1)
- #define unlikely(x) __builtin_expect(!!(x), 0)
-
- int main(char *argv[], int argc)
- {
- int a;
-
- /* Get the value from somewhere GCC can't optimize */
- a = atoi (argv[1]);
-
- if (unlikely (a == 2))
- {
- a++;
- }
- else
- {
- a--;
- }
- printf ("%d\n", a);
-
- return 0;
- }
if else 句型编译后, 一个分支的汇编代码紧随前面的代码,而另一个分支的汇编代码需要使用JMP指令才能访问到.
很明显通过JMP访问需要更多的时间, 在复杂的程序中,有很多的if else句型,又或者是一个有if else句型的库函数,每秒钟被调用几万次,
通常程序员在分支预测方面做得很糟糕, 编译器又不能精准的预测每一个分支,这时JMP产生的时间浪费就会很大,
函数__builtin_expert()就是用来解决这个问题的.
具体从汇编角度来分析其原理的例子,大家可以参照http://kernelnewbies.org/FAQ/LikelyUnlikely,
其对应的中文翻译版见http://velep.com/archives/795.html
本文讲的likely()和unlikely()两个宏,在linux内核代码和一些应用中可常见到它们的身影。实质上,这两个宏是关于GCC编译器内置宏__builtin_expect的使用。
顾名思义,likely()指“很有可能”之意,而unlikely()指“不太可能”之意。那么,在实际应用中,它们代表什么?又是怎么使用的呢?下面是一篇外文翻译(加上了本人的一些理解),给出了详细答案。
对于linux内核代码,在条件判断语句中经常看到likely()和unlikely()的调用,如下代码所示:
- #define likely(x) __builtin_expect(!!(x), 1)
- #define unlikely(x) __builtin_expect(!!(x), 0)
-
- 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文件中可以找到:
在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 return value is the value of EXP, which should be an integral
- expression. The value of C must be a compile-time constant. The
- semantics of the built-in are that it is expected that EXP == C.
- For example:
- if (__builtin_expect (x, 0))
- foo ();
- would indicate that we do not expect to call `foo', since we
- expect `x' to be zero. Since you are limited to integral
- expressions for EXP, you should use constructions such as
- if (__builtin_expect (ptr != NULL, 1))
- error ();
- when testing pointer or floating-point values.
__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反汇编,查看它的汇编代码。
- 080483b0 <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(),重新编译,再来看它的汇编代码:
- 080483b0 <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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。