Chinaunix首页 | 论坛 | 博客
  • 博客访问: 413515
  • 博文数量: 96
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 415
  • 用 户 组: 普通用户
  • 注册时间: 2015-05-22 09:08
个人简介

最近的研究方向:Nginx

文章分类
文章存档

2017年(2)

2016年(59)

2015年(35)

我的朋友

分类: LINUX

2016-12-21 19:36:10

   在内核代码中时常 会看到unlikely和likely的踪影。他们实际上是解释在 linux/compiler.h 中的两个宏。

点击(此处)折叠或打开

  1. #define likely(x) __builtin_exp ect(!!(x), 1)
  2. #define unlikely(x) __builtin_exp ect(!!(x), 0)


   这里的__built_exp ect()函数是gcc的内建函数。

   ____builtin_expect是gcc编译器(版本>=2.96)提供给程序员使用,目的是使得程序员可以把分支预测的信息提供给编译器,以降低因为指令跳转带来的分支下降,它的返回值就是它的第一个参数(这个参数必须是整数)传给它的值。

   所以在linux2.6.38中,____builtin_expect的返回值就是x的值,所以:

    if(likely(value))  等价于 if(value)

     if(unlikely(value))等价于 if(value)

这样我们在阅读代码时,就可以把if(likely(value)),if(unlikely(value))看做if(value),便于我们阅读代码。至于为什么要在内核代码中运用 这两个宏,首要 的目标 是为了实行 代码的优化,提高系统执行速度。


__builtin_expect()的原理分析

  __builtin_expect()的机制,在gcc使用手册中有下面一则例子:

点击(此处)折叠或打开

  1. if(__builtin_expect (x, 0))
  2.     foo ();


这段程序暗示我们,既然我们期望__builtin_expect (x, 0)返回值为0,那么我们不希望调用foo()函数。

   __builtin_expect()在GCC的官方文档中解释如下:  

点击(此处)折叠或打开

  1. Built-in Function: long __builtin_expect (long EXP, long C)
  2. You may use `__builtin_expect' to provide the compiler with branch
  3. prediction information. In general, you should prefer to use
  4. actual profile feedback for this (`-fprofile-arcs'), as
  5. programmers are notoriously bad at predicting how their programs
  6. actually perform. However, there are applications in which this
  7. data is hard to collect.
  8. The return value is the value of EXP, which should be an integral
  9. expression. The value of C must be a compile-time constant. The
  10. semantics of the built-in are that it is expected that EXP == C.
  11. For example:
  12. if (__builtin_expect (x, 0))
  13. foo ();
  14. would indicate that we do not expect to call `foo', since we
  15. expect `x' to be zero. Since you are limited to integral
  16. expressions for EXP, you should use constructions such as
  17. if (__builtin_expect (ptr != NULL, 1))
  18. error ();
  19. when testing pointer or floating-point values.
 GCC的内建方法会判断 EXP == C 是否成立,成立则将if分支中的执行语句紧跟放在汇编跳转指令之后,否则将else分支中的执行语句紧跟汇编跳转指令之后。如下例子所示:

点击(此处)折叠或打开

  1. //test.c
  2. #define likely(x) __builtin_expect(!!(x),1)
  3. #define unlikely(x) __builtin_expect(!!(x),0)
  4. int test_unlikely(int x)
  5. {
  6.     if(unlikely(x == 2))
  7.     {
  8.         x++;
  9.     }
  10.     else
  11.     {
  12.         x--;
  13.     }
  14.     return x;
  15. }
  16. int test_likely(int x)
  17. {
  18.     if(likely(x == 2))
  19.     {
  20.         x++;
  21.     }
  22.     else
  23.     {
  24.         x--;
  25.     }
  26.     return x;
  27. }

编译并导出目标文件的汇编表示:

gcc -fprofile-arcs -O2 -c test.c

objdump -d test.o

得到如下汇编:

点击(此处)折叠或打开

  1. test.o: file format elf32-i386
  2. Disassembly of section .text:
  3. 00000000 <test_likely>:
  4.    0: 55 push %ebp
  5.    1: 83 05 00 00 00 00 01 addl $0x1,0x0
  6.    8: 89 e5 mov %esp,%ebp
  7.    a: 83 15 04 00 00 00 00 adcl $0x0,0x4
  8.   11: 83 7d 08 02 cmpl $0x2,0x8(%ebp) //留意这里!!!判断 x == 2
  9.   15: 75 15 jne 2c <test_likely+0x2c> //跳转指令jne!!!x != 2 时才跳转!
  10.   17: 83 05 08 00 00 00 01 addl $0x1,0x8 //if分支代码 x++
  11.   1e: b8 03 00 00 00 mov $0x3,%eax
  12.   23: 83 15 0c 00 00 00 00 adcl $0x0,0xc
  13.   2a: 5d pop %ebp
  14.   2b: c3 ret
  15.   2c: 8b 45 08 mov 0x8(%ebp),%eax //跳转到这里,else分支代码 x--
  16.   2f: 5d pop %ebp
  17.   30: 83 e8 01 sub $0x1,%eax
  18.   33: 83 05 10 00 00 00 01 addl $0x1,0x10
  19.   3a: 83 15 14 00 00 00 00 adcl $0x0,0x14
  20.   41: c3 ret
  21.   42: 8d b4 26 00 00 00 00 lea 0x0(%esi),%esi
  22.   49: 8d bc 27 00 00 00 00 lea 0x0(%edi),%edi
  23. 00000050 <test_unlikely>:
  24.   50: 55 push %ebp
  25.   51: 89 e5 mov %esp,%ebp
  26.   53: 8b 45 08 mov 0x8(%ebp),%eax
  27.   56: 83 05 18 00 00 00 01 addl $0x1,0x18
  28.   5d: 83 15 1c 00 00 00 00 adcl $0x0,0x1c
  29.   64: 83 f8 02 cmp $0x2,%eax //留意这里!!!判断 x == 2
  30.   67: 74 13 je 7c <test_unlikely+0x2c> //跳转指令je!!!x == 2 时就跳转!
  31.   69: 83 e8 01 sub $0x1,%eax //else分支代码 x--
  32.   6c: 83 05 28 00 00 00 01 addl $0x1,0x28
  33.   73: 83 15 2c 00 00 00 00 adcl $0x0,0x2c
  34.   7a: 5d pop %ebp
  35.   7b: c3 ret
  36.   7c: 83 05 20 00 00 00 01 addl $0x1,0x20 //跳转到这里,if分支代码 x++
  37.   83: b0 03 mov $0x3,%al
  38.   85: 83 15 24 00 00 00 00 adcl $0x0,0x24
  39.   8c: 5d pop %ebp
  40.   8d: c3 ret
  41.   8e: 66 90 xchg %ax,%ax
  42. 00000090 <_GLOBAL__I_0_test_unlikely>:
  43.   90: 55 push %ebp
  44.   91: 89 e5 mov %esp,%ebp
  45.   93: 83 ec 08 sub $0x8,%esp
  46.   96: c7 04 24 00 00 00 00 movl $0x0,(%esp)
  47.   9d: e8 fc ff ff ff call 9e <_GLOBAL__I_0_test_unlikely+0xe>
  48.   a2: c9 leave
  49.   a3: c3 ret 


  注意:likely和unlikely所生成的跳转指令是不同的,分别是jne和je.

  如上述例子分析所示,宏likely和宏unlikely唯一的作用就是选择“将if分支还是else分支放在跳转指令之后,从而优化程序的执行效率”。 因为likely(EXP)代表条件表达式EXP很可能成立,而unlikely(EXP)代表条件表达式EXP很可能不成立,当程序员清楚EXP表达式 多数情况成立(不成立)时,就可使用likely(unlikely),使if分支(else分支)紧跟跳转指令其后,从而在大多数情况下不用执行跳转指 令,避开跳转指令所带来的开销,从而达到优化的目的。

   linux内核中的likely宏和unlikely宏使用举例

  举例分析 :

点击(此处)折叠或打开

  1. if (likely(a>b))
  2. {
  3.   fun1();
  4. }
  5. if (unlikely(a<b))
  6. {
  7.   fun2();
  8. }

这里就是程序员可以确定 a>b 在程序执行流程 中出现的可能相比较大,因此运用 了likely()告诉编译器将fun1()函数的二进制代码紧跟在前面程序的后面,这样就cache在预取数据时就可以将fun1()函数的二进制代码 拿到cache中。这样,也就添加 了cache的命中率。

  同样的,unlikely()的作用就是告诉编译器,a

  咱们 不用对likely和unlikely感到迷惑,须要 知晓 的就是 if(likely(a>b)) 和 if(a>b)在功能上是等价的,同样 if(unlikely(a

  比如下面的代码:

    

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #define unlikely(x) __builtin_exp ect(!!(x), 0)
  3. #define likely(x) __builtin_exp ect(!!(x), 1)
  4. int main()
  5. {
  6.     int a=2,b=4;
  7.     if(unlikely(a<b)) {
  8.         printf("in the unlikely,is not your exp ecting!\n");
  9.     } else {
  10.         printf("in the unlikely, is your exp ecting\n");
  11.     }
  12.     if(likely(a<b)) {
  13.         printf("in the likely, is your exp ecting\n");
  14.     }
  15.     return 0;
  16. }

阅读(2377) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~