C/C++ 分支预测(likely unlikely)
看一些代码时,会遇到likely unlikely, 查了查网上的资料,结合自己的理解记录一下。
1. 一些概念
指令周期是指执行一条指令所需要的时间,一般由若干个机器周期组成,是从取指令、分析指令到指令执行完所需的全部。
预取指令具体方法就是在不命中时,当数据从主存储器中取出送往CPU的同时,把主存储器相邻几个单元中的数据(称为一个数据块)都取出来送入Cache中。预取指令可以更好的利用 cpu资源。简单说就是从内存取指令很慢, cpu要等待这个过程。如果能提前预测可能执行的指令,就提前从内存把指令读到 cache, 由于 cache的访问速度较内存快,cpu要执行时就不用等很长时间了。
如果开发人员可以告诉编译器,哪个分支更有可能发生(likely) 或者 非常不可能发生(unlikely), 可以帮助编译器进行代码编译。
2. 看看代码
unlikely.cpp:
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 #define likely(x) __builtin_expect(!!(x), 1) //gcc内置函数, 帮助编译器分支优化 5 #define unlikely(x) __builtin_expect(!!(x), 0) 6 7 int main(int argc, char* argv[]){ 8 int x = 0; 9 x = atoi(argv[1]); 10 11 if (unlikely(x == 3)){ //告诉编译器这个分支非常不可能为true 12 x = x + 9; 13 } 14 else{ 15 x = x - 8; 16 } 17 18 printf("x=%d\n", x); 19 return 0; 20 }
3. 分析一下
gcc版本:gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
编译:gcc -O2 unlikely.cpp -o unlikely
反汇编一下,看看汇编:objdump -S unlikely
1 08048380 <main>: 2 8048380: 55 push %ebp 3 8048381: 89 e5 mov %esp,%ebp 4 8048383: 83 e4 f0 and $0xfffffff0,%esp 5 8048386: 83 ec 10 sub $0x10,%esp 6 8048389: 8b 45 0c mov 0xc(%ebp),%eax 7 804838c: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp) 8 8048393: 00 9 8048394: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 10 804839b: 00 11 804839c: 8b 40 04 mov 0x4(%eax),%eax 12 804839f: 89 04 24 mov %eax,(%esp) 13 80483a2: e8 c9 ff ff ff call 8048370 <strtol@plt> 14 80483a7: 83 f8 03 cmp $0x3,%eax 15 80483aa: 74 1f je 80483cb <main+0x4b> 16 80483ac: 83 e8 08 sub $0x8,%eax 17 80483af: 89 44 24 08 mov %eax,0x8(%esp) 18 80483b3: c7 44 24 04 60 85 04 movl $0x8048560,0x4(%esp) 19 80483ba: 08 20 80483bb: c7 04 24 01 00 00 00 movl $0x1,(%esp) 21 80483c2: e8 99 ff ff ff call 8048360 <__printf_chk@plt> 22 80483c7: 31 c0 xor %eax,%eax 23 80483c9: c9 leave 24 80483ca: c3 ret 25 80483cb: b8 0c 00 00 00 mov $0xc,%eax 26 80483d0: eb dd jmp 80483af <main+0x2f> 27 80483d2: 90 nop 28 80483d3: 90 nop
我们从汇编代码可以看到,代码并不是按照顺序生成的。
unlikely分支(x==3)非常不可能发生,汇编代码生成到了最后。
这个对于庞大的代码还是非常有用的,毕竟在代码预期阶段,可以根据局部性原理把最可能发生的分支对应的指令缓存进来。