linux likely and unlikely解析

来源:互联网 发布:雄迈cms客户端下载 编辑:程序博客网 时间:2024/05/17 23:43
看内核时总遇到if(likely( )){}或是if(unlikely( ))这样的语句,最初不解其意,现在有所了解,所以也想介绍一下。

likely() 与 unlikely()是内核(我看的是2.6.22.6版本,2.6的版本应该都有)中定义的两个宏。位于/include/linux/compiler.h中,
具体定义如下:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

__builtin_expect是gcc(版本>=2.96,网上写的,我没验证过)中提供的一个预处理命令(这个名词也是网上写的,我想叫函数更好些),有利于代码优化。gcc(version 4.4.0)具体定义如下:
long __builtin_expect (long exp, long c) [Built-in Function]

注解为:
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 semantics of the built-in are that it is expected that exp == c.

它的意思是:我们可以使用这个函数人为告诉编绎器一些分支预测信息“exp==c” 是“很可能发生的”。

#define likely(x) __builtin_expect(!!(x), 1)也就是说明x==1是“经常发生的”或是“很可能发生的”。
使用likely ,执行if后面语句的可能性大些,编译器将if{}是的内容编译到前面, 使用unlikely ,执行else后面语句的可能性大些,编译器将else{}里的内容编译到前面。这样有利于cpu预取,提高预取指令的正确率,因而可提高效率。

举个例子(内核版本2.6.22.6):/kernel/shed.c中有一段:
if (likely(!active_balance)) {
/* We were unbalanced, so reset the balancing interval */
sd->balance_interval = sd->min_interval;
} else {
/*
* If we've begun active balancing, start to back off. This
* case may not be covered by the all_pinned logic if there
* is only 1 task on the busy runqueue (because we don't call
* move_tasks).
*/
if (sd->balance_interval max_interval)
sd->balance_interval *= 2;
}

编译过程中,会将if后面{}里的内容编译到前面,else 后面{}里的内容编译到后面。若将likely换成unlikely 则正好相反。

总之,likely与unlikely互换或不用都不会影响程序的正确性。但可能会影响程序的效率。


if(likely(foo))  //认为foo通常为1

if(unlikely(foo)) //认为foo通常为0


感谢各位光顾!
不知道有没有写清楚,望指正!
疑惑:
为什么likely或是unlikely要定义成__builtin_expect(!!(x), 1),而不直接用__builtin_expect(x, 1)?" !!(x) "与" x "有什么不同?

另外内核2.6.31.5中likely和unlikely还有一种定义:
# ifndef likely
# define likely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1))
# endif

# ifndef unlikely

# define unlikely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0))




linux中判断语句经常会看到likely和unlikely,例如:

  if(likely(value)){
  }
  else{
  }
  简单从表面上看if(likely(value)) == if(value),if(unlikely(value)) == if(value)。
  也就是likely和unlikely是一样的,但是实际上执行是不同的,加likely的意识是value的值为真的可能
  性更大一些,那么执行if的机会大,而unlikely表示value的值为假的可能性大一些,执行else机会大一些。
  加上这种修饰,编译成二进制代码时likely使得if后面的执行语句紧跟着前面的程序,unlikely使得else后
  面的语句紧跟着前面的程序,这样就会被cache预读取,增加程序的执行速度,likely和unlikely的实现在
  include/linux/compiler.h中:
  9 #if __GNUC__ == 2 && __GNUC_MINOR__ < 96
  10 #define __builtin_expect(x, expected_value) (x)
  11 #endif
  12
  13 #define likely(x) __builtin_expect((x),1)
  14 #define unlikely(x) __builtin_expect((x),0)
  __builtin_expect是gcc的一个预处理命令,其解释如下:
  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.








 在 2.6 内核中,随处可以见到 likely() 和 unlikely() 的身影,那么为什么要用它们?它们之间有什么区别?

  首先要明确:

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

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

  也就是说 likely() 和 unlikely() 从阅读和理解代码的角度来看,是一样的!!!

  这两个宏在内核中定义如下:

  #define likely(x) __builtin_expect((x),1)

  #define unlikely(x) __builtin_expect((x),0)

  __builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。

  __builtin_expect((x),1) 表示 x 的值为真的可能性更大;

  __builtin_expect((x),0) 表示 x 的值为假的可能性更大。

  也就是说,使用 likely() ,执行 if 后面的语句 的机会更大,使用unlikely(),执行else 后面的语句的机会更大。

  例如下面这段代码,作者就认为 prev 不等于 next 的可能性更大,

  if (likely(prev != next)) {

  next->timestamp = now;

  ...

  } else {

  ...;

  }

  通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。

  下面以两个例子来加深这种理解:

  第一个例子: example1.c

  int testfun(int x)

  {

  if(__builtin_expect(x, 0)) {

  ^^^--- We instruct the compiler, "else" block is more probable

  x = 5;

  x = x * x;

  } else {

  x = 6;

  }

  return x;

  }

  在这个例子中,我们认为 x 为0的可能性更大

  编译以后,通过 objdump 来观察汇编指令,在我的 2.4 内核机器上,结果如下:

  # gcc -O2 -c example1.c

  # objdump -d example1.o

  Disassembly of section .text:

  00000000 :

  0: 55 push %ebp

  1: 89 e5 mov %esp,%ebp

  3: 8b 45 08 mov 0x8(%ebp),%eax

  6: 85 c0 test %eax,%eax

  8: 75 07 jne 11

  a: b8 06 00 00 00 mov $0x6,%eax

  f: c9 leave

  10: c3 ret

  11: b8 19 00 00 00 mov $0x19,%eax

  16: eb f7 jmp f

  可以看到,编译器使用的是 jne (不相等跳转)指令,并且 else block 中的代码紧跟在后面。

  8: 75 07 jne 11

  a: b8 06 00 00 00 mov $0x6,%eax

  第二个例子: example2.c

  int testfun(int x)

  {

  if(__builtin_expect(x, 1)) {

  ^^^ --- We instruct the compiler, "if" block is more probable

  x = 5;

  x = x * x;

  } else {

  x = 6;

  }

  return x;

  }

  在这个例子中,我们认为 x 不为 0 的可能性更大

  编译以后,通过 objdump 来观察汇编指令,在我的 2.4 内核机器上,结果如下:

  # gcc -O2 -c example2.c

  # objdump -d example2.o

  Disassembly of section .text:

  00000000 :

  0: 55 push %ebp

  1: 89 e5 mov %esp,%ebp

  3: 8b 45 08 mov 0x8(%ebp),%eax

  6: 85 c0 test %eax,%eax

  8: 74 07 je 11

  a: b8 19 00 00 00 mov $0x19,%eax

  f: c9 leave

  10: c3 ret

  11: b8 06 00 00 00 mov $0x6,%eax

  16: eb f7 jmp f

  这次编译器使用的是 je (相等跳转)指令,并且 if block 中的代码紧跟在后面。

  8: 74 07 je 11

  a: b8 19 00 00 00 mov $0x19,%eax



linux中判断语句经常会看到likely和unlikely,例如:

if(likely(value)){

}

else{

}

简单从表面上看if(likely(value)) == if(value),if(unlikely(value)) == if(value)。

也就是likely和unlikely是一样的,但是实际上执行是不同的,加likely的意识是value的值为真的可能

性更大一些,那么执行if的机会大,而unlikely表示value的值为假的可能性大一些,执行else机会大一些。

加上这种修饰,编译成二进制代码时likely使得if后面的执行语句紧跟着前面的程序,unlikely使得else后

面的语句紧跟着前面的程序,这样就会被cache预读取,增加程序的执行速度,likely和unlikely的实现在

include/linux/compiler.h中:

        9 #if __GNUC__ == 2 && __GNUC_MINOR__ < 96
       10 #define __builtin_expect(x, expected_value) (x)
       11 #endif
       12
       13 #define likely(x)     __builtin_expect((x),1)
       14 #define unlikely(x) __builtin_expect((x),0)

__builtin_expect是gcc的一个预处理命令,其解释如下:

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.

 

likely和unlikely是gcc扩展的跟处理器相关的宏:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
现在处理器都是流水线的,有些里面有多个逻辑运算单元,系统可以提前取多条指令进行并行处理,但遇到跳转时,则需要重新取指令,这相对于不用重新去指令就降低了速度。
所以就引入了likely和unlikely,目的是增加条件分支预测的准确性,cpu会提前装载后面的指令,遇到条件转移指令时会提前预测并装载某个分支的指令。unlikely 表示你可以确认该条件是极少发生的,相反likely表示该条件多数情况下会发生。编译器会产生相应的代码来优化cpu执行效率。
因此程序员在编写代码时可以根据判断条件发生的概率来优化处理器的取指操作。
例如:
int x, y;
if(unlikely(x > 0))
y = 1;
else
y = -1;

上面的代码中gcc编译的指令会预先读取 y = -1 这条指令,这适合 x 的值大于 0 的概率比较小的情况。
如果 x 的值在大部分情况下是大于 0 的,就应该用 likely(x > 0),这样编译出的指令是预先读取 y = 1 这条指令了。这样系统在运行时就会减少重新取指了。




原创粉丝点击