在以往我们做分支判断时一般使用if else进行分支的判断常见如下demo
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char ** argv)
{
int a = atoi(argv[1]);
if (a > 0){
a = 0x5a;
}else {
a = 0xa5;
}
printf("a = 0x%x\n", a);
return 0;
}
但是在Linux内核当中会看到大量的likely、unlikely这一类的判断,这里将介绍这类判断的设计的原因及如何使用。
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
一、likely、unlikely设计的原因
在 c语言当中!(x)取反,!!(x)在取反的基础上再次取反,不同的是假设x = 100, !(x) =0, !!(x) = 1,进一步分析假设X= -1; !(x) = 1, !!(x) = 0;这样无论输入的参数是多少结果就变成了bool值true, false。
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 semantics of the built-in are that it is expected that exp == c. For example:
if (__builtin_expect (x, 0))
foo ();
indicates 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))
foo (*ptr);
详细描述参见:https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
期望exp == c, 举例:
1、假设x == 0, 说明if (__builtin_expect (x, 0)) 判断期望不运行foo();
2、假设ptr != NULL,则说明ptr 倾向于非空 这样判断期望运行foo(*ptr);
所以针对likely, unlikely所代表的含义就如下:
# define likely(x) __builtin_expect(!!(x), 1) //x 的值为真的可能性更大,对应的运行可能的代码分支
# define unlikely(x) __builtin_expect(!!(x), 0) //x 的值为假的可能性更大,对应不太可能的代码分支
**You may use __builtin_expect to provide the compiler with branch prediction information **
从这里可以知道likely,unlikely起到了分支预测的功能,通过分支预测提前加载可能运行的指令提高代码的运行效率。
二、反汇编验证
#include <stdio.h>
#include <stdlib.h>
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
int main(int argc, char ** argv)
{
int a = atoi(argv[1]);
if (likely(a > 0)){
a = 0x5a;
}else {
a = 0xa5;
}
printf("a = 0x%x\n", a);
return 0;
}
大概率a > 0为真 ,if likely大概率为真,则分支预取运行X = 0x5a;
gcc -fprofile-arcs -O2 -c likely.c
objdump -S likely.o
反汇编结果如下:
0000000000000000 <main>:
0: 48 83 ec 08 sub $0x8,%rsp
4: 48 8b 7e 08 mov 0x8(%rsi),%rdi
8: 31 f6 xor %esi,%esi
a: ba 0a 00 00 00 mov $0xa,%edx
f: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 17 <main+0x17>
16: 01
17: e8 00 00 00 00 callq 1c <main+0x1c>
1c: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 24 <main+0x24>
23: 01
24: be 5a 00 00 00 mov $0x5a,%esi //率先执行a=0x5a
29: 85 c0 test %eax,%eax
2b: 7e 1b jle 48 <main+0x48>
2d: bf 00 00 00 00 mov $0x0,%edi
32: 31 c0 xor %eax,%eax
34: e8 00 00 00 00 callq 39 <main+0x39>
39: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 41 <main+0x41>
40: 01
41: 31 c0 xor %eax,%eax
43: 48 83 c4 08 add $0x8,%rsp
47: c3 retq
48: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 50 <main+0x50>
4f: 01
50: be a5 00 00 00 mov $0xa5,%esi //后执行a = 0xa5
55: eb d6 jmp 2d <main+0x2d>
57: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
5e: 00 00
#include <stdio.h>
#include <stdlib.h>
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
int main(int argc, char ** argv)
{
int a = atoi(argv[1]);
if (unlikely(a > 0)){
a = 0x5a;
}else {
a = 0xa5;
}
printf("a = 0x%x\n", a);
return 0;
}
大概率a < 0为假, if unlikely大概率为假,则分支预取运行X = 0xa5;
gcc -fprofile-arcs -O2 -c unlikely.c
objdump -S unlikely.o
反汇编结果如下:
0000000000000000 <main>:
0: 48 83 ec 08 sub $0x8,%rsp
4: 48 8b 7e 08 mov 0x8(%rsi),%rdi
8: 31 f6 xor %esi,%esi
a: ba 0a 00 00 00 mov $0xa,%edx
f: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 17 <main+0x17>
16: 01
17: e8 00 00 00 00 callq 1c <main+0x1c>
1c: 85 c0 test %eax,%eax
1e: 7f 28 jg 48 <main+0x48>
20: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 28 <main+0x28>
27: 01
28: be a5 00 00 00 mov $0xa5,%esi //率先执行a = 0xa5
2d: bf 00 00 00 00 mov $0x0,%edi
32: 31 c0 xor %eax,%eax
34: e8 00 00 00 00 callq 39 <main+0x39>
39: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 41 <main+0x41>
40: 01
41: 31 c0 xor %eax,%eax
43: 48 83 c4 08 add $0x8,%rsp
47: c3 retq
48: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 50 <main+0x50>
4f: 01
50: be 5a 00 00 00 mov $0x5a,%esi //后执行a = 0x5a
55: eb d6 jmp 2d <main+0x2d>
57: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
5e: 00 00
三、总结
1、likely,unlikely同if else一样进行分支判断;likely 期望执行if (true)为真的分支;unlikely 期望执行 if (true) 当中的else分支, 直接运 行else分支代码。
2、likely, unlikely 通过分支预测指令的预取能提高代码的执行效率。 但是前提在使用的过程当中程序的开发者必须对自己的代码逻 辑有清晰的认识,知道什么样的逻辑会大概率执行,什么样的逻辑大概率不会执行,只有这样才能通likely,unlikely 的判断做精准的分 支预测,提高程序的运行性能。