今天晚上看书的时候遇到了likely与unlikely这两个东西,书上大概是说这东西一般放到if条件判断中来优化条件分枝,比如说:foo变量在大多情况下都为非0,那么判断的时候可以这样优化
if (likely(foo))
{
/* do something */
}
else
{
/* do something else */
}
|
反之,如果foo在大多情况下都为0,那么就用unlikely代替likely。
本来想睡觉了,电脑都关了。刷完牙后又想到了这个东西觉得有必要看一下它到底怎么回事。
原来这两个东西是宏定义:
#define likely(exp) __builtin_expect((exp),1)
#define unlikely(exp) __builtin_expect((exp),0)
|
__builtin_expect是gcc内置的"函数",带有2个参数,第一个参数为一个表达式,第二个参数为一个编译期常量,返回值是表达式的值。这个函数的语义是:你期望的表达式的值是这个常量的值。
我想知道这东西是怎样优化分支的,用下边一段代码进行测试:
#include <stdio.h>
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
int main()
{
int i;
volatile int n; /*加volatile是为了产生下边两个赋值的汇编指令,不然编译器优化产生一些我们不想要的结果*/
scanf("%d", &i);
if (unlikely(i))
n = 3;
else
n = 5;
return n;
}
|
编译的时候选择优化编译
gcc -g -O2 test.c -o test |
得到的汇编代码如下:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400550 <main+0>: sub $0x18,%rsp
0x0000000000400554 <main+4>: xor %eax,%eax
0x0000000000400556 <main+6>: mov $0x40067c,%edi
0x000000000040055b <main+11>: lea 0xc(%rsp),%rsi
0x0000000000400560 <main+16>: callq 0x400450 <__isoc99_scanf@plt>
0x0000000000400565 <main+21>: mov 0xc(%rsp),%eax
0x0000000000400569 <main+25>: test %eax,%eax
0x000000000040056b <main+27>: jne 0x40057e <main+46>
0x000000000040056d <main+29>: movl $0x5,0x8(%rsp) //n = 5
0x0000000000400575 <main+37>: mov 0x8(%rsp),%eax
0x0000000000400579 <main+41>: add $0x18,%rsp
0x000000000040057d <main+45>: retq
0x000000000040057e <main+46>: movl $0x3,0x8(%rsp) //n = 3
0x0000000000400586 <main+54>: jmp 0x400575 <main+37>
End of assembler dump.
|
在代码中我们用unlikely,期望else分支(n=5)会执行。所以在判断了条件后紧接着的就是else分支的代码,而把if分支的代码放到了后边。这样在大多数情况下(也就是期望情况下)程序都会顺序执行不需要跳转,这样就可以提高效率。(cache命中率高)
将unlikely改为likely后,得到的汇编代码如下:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400550 <main+0>: sub $0x18,%rsp
0x0000000000400554 <main+4>: xor %eax,%eax
0x0000000000400556 <main+6>: mov $0x40067c,%edi
0x000000000040055b <main+11>: lea 0xc(%rsp),%rsi
0x0000000000400560 <main+16>: callq 0x400450 <__isoc99_scanf@plt>
0x0000000000400565 <main+21>: mov 0xc(%rsp),%eax
0x0000000000400569 <main+25>: test %eax,%eax
0x000000000040056b <main+27>: je 0x40057e <main+46>
0x000000000040056d <main+29>: movl $0x3,0x8(%rsp)
0x0000000000400575 <main+37>: mov 0x8(%rsp),%eax
0x0000000000400579 <main+41>: add $0x18,%rsp
0x000000000040057d <main+45>: retq
0x000000000040057e <main+46>: movl $0x5,0x8(%rsp)
0x0000000000400586 <main+54>: jmp 0x400575 <main+37>
End of assembler dump.
|
这回我们认为大多情况下都会执行if分支,所以if分支的代码紧接着条件判断之后(其实不采取任何优化也是这个效果,因为if分支本来就在else前边

),所以程序在大多情况下都会顺序执行不需要跳转。
__builtin_expect是gcc加入的一些新特性之一,其实在linux内核源代码里很多地方可以见到。我没有真正去看过内核代码,所以到现在才发现
阅读(692) | 评论(0) | 转发(0) |