在内核代码中时常 会看到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
咱们 不用对likely和unlikely感到迷惑,须要 知晓 的就是 if(likely(a>b)) 和 if(a>b)在功能上是等价的,同样 if(unlikely(a
比如下面的代码:
-
#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 ecting\n");
-
}
-
if(likely(a<b)) {
-
printf("in the likely, is your exp ecting\n");
-
}
-
return 0;
-
}
阅读(2377) | 评论(0) | 转发(0) |