__builtin_expect 主要用于减少条件语句中的汇编级别的跳转,增加代码的执行效率,典型的空间换时间。下面使用一个简单的测试代码作为演示:
#if defined __GNUC__ || defined __llvm__
#define sf_likely(x) __builtin_expect ((x), 1)
#define sf_unlikely(x) __builtin_expect ((x), 0)
#else
#define sf_likely(x) (x)
#define sf_unlikely(x) (x)
#endif
int test_likely(int x){
int return_code;
if(sf_likely(x)){
return_code = 11;
}else{
return_code = 22;
}
return return_code;
}
int test_unlikely(int x){
int return_code;
if(sf_unlikely(x)){
return_code = 11;
}else{
return_code = 22;
}
return return_code;
}
int test(int x){
int return_code;
if(x){
return_code=11;
}else{
return_code=22;
}
return return_code;
}
由于平时开发有使用o2优化级别,因此我们也使用这个级别应的汇编代码来了解它的作用。
编译命令
gcc -fprofile-arcs -O2 -c test.c
查看反汇编代码
objdump -d test.o
Disassembly of section .text:
0000000000000000 <test_likely>:
0: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 8 <test_likely+0x8>
7: 01
8: 85 ff test %edi,%edi
a: 74 06 je 12 <test_likely+0x12>
c: b8 0b 00 00 00 mov $0xb,%eax
11: c3 retq
12: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 1a <test_likely+0x1a>
19: 01
1a: b8 16 00 00 00 mov $0x16,%eax
1f: c3 retq
0000000000000020 <test_unlikely>:
20: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 28 <test_unlikely+0x8>
27: 01
28: 85 ff test %edi,%edi
2a: 75 0e jne 3a <test_unlikely+0x1a>
2c: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 34 <test_unlikely+0x14>
33: 01
34: b8 16 00 00 00 mov $0x16,%eax
39: c3 retq
3a: b8 0b 00 00 00 mov $0xb,%eax
3f: c3 retq
0000000000000040 <test>:
40: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 48 <test+0x8>
47: 01
48: 85 ff test %edi,%edi
4a: b8 0b 00 00 00 mov $0xb,%eax
4f: 75 0a jne 5b <test+0x1b>
51: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 59 <test+0x19>
58: 01
59: b0 16 mov $0x16,%al
5b: f3 c3 repz retq
对比三者之间的差异就可以发现执行效率的不同,显然如果if分支的概率比较高的话,使用likely会带来性能的提升,反之如果else分支的概率比较高的话,使用unlikely就会减少很多跳转指令,提升了性能,这里面使用了一种思想,就是“概率”,把主要的处理放在大概率事件上,减少了跳转 ,优化了代码,减少了执行指令的时间。乍看之下,似乎作用不明显,可是在高并发环境下却会有意想不到的效果,数量产生质量!