这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。这个指令的写法为:__builtin_expect(EXP, N)。 意思是:EXP==N的概率很大。

一般的使用方法是将__builtin_expect指令封装为likelyunlikely宏。这两个宏的写法如下.

  1. #define likely(x) __builtin_expect(!!(x), 1) //x很可能为真
  2. #define unlikely(x) __builtin_expect(!!(x), 0) //x很可能为假

!!:第一次!

内核中的 likely() 与 unlikely()

首先要明确:

  1. if(likely(value)) //等价于 if(value)
  2. if(unlikely(value)) //也等价于 if(value)

__builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
__builtin_expect((x),1)表示 x 的值为真的可能性更大;
__builtin_expect((x),0)表示 x 的值为假的可能性更大。
也就是说,使用likely(),执行 if 后面的语句的机会更大,使用 unlikely(),执行 else 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。

例子

  1. /* test_builtin_expect.c */
  2. int test_likely(int x)
  3. {
  4. if(LIKELY(x))
  5. {
  6. x = 5;
  7. }
  8. else
  9. {
  10. x = 6;
  11. }
  12. return x;
  13. }
  14. int test_unlikely(int x)
  15. {
  16. if(UNLIKELY(x))
  17. {
  18. x = 5;
  19. }
  20. else
  21. {
  22. x = 6;
  23. }
  24. return x;
  25. }
  • 编译指令

    1. gcc -fprofile-arcs -O2 -c test_builtin_expect.c
    2. objdump -d test_builtin_expect.o
  • 结果

image.png

汇编小解:

  • je【Jump if Equals】,在ZF被置位时跳转。je 是 jz 【Jump if Zero】的别名。
  • test 指令用于两个操作数的按位AND运算,并根据结果设置标志寄存器,结果本身不会写回到目的操作数。
  1. /* 代码解释 */
  2. test %edi, %edi ; set ZF to 1 if cl == 0
  3. je 0x804f430 ; jump if ZF == 1
  4. /* 等价于如下C代码 */
  5. if(edi==0){
  6. goto 0x804f430
  7. }

显然, 两次函数编译生成的汇编语句所使用的跳转指令不同。分析得知,__builtin_expect可尽量减少不必要的跳转。
__builtin_expect仅告诉编译器优化,并无改变其对真值的判断