linux内核中的likely宏和unlikely宏介绍
在内核代码中时常 会看到unlikely和likely的踪影。他们实际上是解释在 linux/compiler.h 中的两个宏。
#define likely(x) __builtin_exp ect(!!(x), 1)
#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使用手册中有下面一则例子:
if (__builtin_expect (x, 0))
foo ();
这段程序暗示我们,既然我们期望__builtin_expect (x, 0)返回值为0,那么我们不希望调用foo()函数。
__builtin_expect()在GCC的官方文档中解释如下:
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.
GCC的内建方法会判断 EXP == C 是否成立,成立则将if分支中的执行语句紧跟放在汇编跳转指令之后,否则将else分支中的执行语句紧跟汇编跳转指令之后。如下例子所示:
//test.c
#define likely(x) __builtin_expect(!!(x),1)
#define unlikely(x) __builtin_expect(!!(x),0)
int test_unlikely(int x)
{
if(unlikely(x == 2))
{
x++;
}
else
{
x--;
}
return x;
}
int test_likely(int x)
{
if(likely(x == 2))
{
x++;
}
else
{
x--;
}
return x;
}
编译并导出目标文件的汇编表示:
gcc -fprofile-arcs -O2 -c test.c
objdump -d test.o
得到如下汇编:
test.o: file format elf32-i386
Disassembly of section .text:
00000000 <test_likely>:
0: 55 push %ebp
1: 83 05 00 00 00 00 01 addl $0x1,0x0
8: 89 e5 mov %esp,%ebp
a: 83 15 04 00 00 00 00 adcl $0x0,0x4
11: 83 7d 08 02 cmpl $0x2,0x8(%ebp) //留意这里!!!判断 x == 2
15: 75 15 jne 2c <test_likely+0x2c> //跳转指令jne!!!x != 2 时才跳转!
17: 83 05 08 00 00 00 01 addl $0x1,0x8 //if分支代码 x++
1e: b8 03 00 00 00 mov $0x3,%eax
23: 83 15 0c 00 00 00 00 adcl $0x0,0xc
2a: 5d pop %ebp
2b: c3 ret
2c: 8b 45 08 mov 0x8(%ebp),%eax //跳转到这里,else分支代码 x--
2f: 5d pop %ebp
30: 83 e8 01 sub $0x1,%eax
33: 83 05 10 00 00 00 01 addl $0x1,0x10
3a: 83 15 14 00 00 00 00 adcl $0x0,0x14
41: c3 ret
42: 8d b4 26 00 00 00 00 lea 0x0(%esi),%esi
49: 8d bc 27 00 00 00 00 lea 0x0(%edi),%edi
00000050 <test_unlikely>:
50: 55 push %ebp
51: 89 e5 mov %esp,%ebp
53: 8b 45 08 mov 0x8(%ebp),%eax
56: 83 05 18 00 00 00 01 addl $0x1,0x18
5d: 83 15 1c 00 00 00 00 adcl $0x0,0x1c
64: 83 f8 02 cmp $0x2,%eax //留意这里!!!判断 x == 2
67: 74 13 je 7c <test_unlikely+0x2c> //跳转指令je!!!x == 2 时就跳转!
69: 83 e8 01 sub $0x1,%eax //else分支代码 x--
6c: 83 05 28 00 00 00 01 addl $0x1,0x28
73: 83 15 2c 00 00 00 00 adcl $0x0,0x2c
7a: 5d pop %ebp
7b: c3 ret
7c: 83 05 20 00 00 00 01 addl $0x1,0x20 //跳转到这里,if分支代码 x++
83: b0 03 mov $0x3,%al
85: 83 15 24 00 00 00 00 adcl $0x0,0x24
8c: 5d pop %ebp
8d: c3 ret
8e: 66 90 xchg %ax,%ax
00000090 <_GLOBAL__I_0_test_unlikely>:
90: 55 push %ebp
91: 89 e5 mov %esp,%ebp
93: 83 ec 08 sub $0x8,%esp
96: c7 04 24 00 00 00 00 movl $0x0,(%esp)
9d: e8 fc ff ff ff call 9e <_GLOBAL__I_0_test_unlikely+0xe>
a2: c9 leave
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宏使用举例
举例分析 :
if (likely(a>b)) {
fun1();
}
if (unlikely(a<b)) {
fun2();
}
这里就是程序员可以确定 a>b 在程序执行流程 中出现的可能相比较大,因此运用 了likely()告诉编译器将fun1()函数的二进制代码紧跟在前面程序的后面,这样就cache在预取数据时就可以将fun1()函数的二进制代码 拿到cache中。这样,也就添加 了cache的命中率。
同样的,unlikely()的作用就是告诉编译器,a<b 的可能性很小所以这里在编译时,将fun2()的二进制代码尽量不要和前边的编译在一块。
咱们 不用对likely和unlikely感到迷惑,须要 知晓 的就是 if(likely(a>b)) 和 if(a>b)在功能上是等价的,同样 if(unlikely(a<b)) 和 if(a<b) 的功能也是一样的。不一样的只是他们声称的二进制代码有所不一样 ,这一点咱们 也可以从他们的汇编代码中看到。
比如下面的代码:
#include <stdio.h>
#define unlikely(x) __builtin_exp ect(!!(x), 0)
#define likely(x) __builtin_exp ect(!!(x), 1)
int main()
{
int a=2,b=4;
if(unlikely(a<b)) {
printf("in the unlikely,is not your exp ecting!n");
} else {
printf("in the unlikely, is your exp ectingn");
}
if(likely(a<b)) {
printf("in the likely, is your exp ectingn");
}
return 0;
}
执行结果:
in the unlikely,is not your exp ecting!
in the likely, is your exp ecting
总之,likely和unlikely的功能就是添加 cache的命中率,提高系统执行速度。