简介
- likely与unlikely是内核中定义的两个宏,位于/include/linux/compiler.h中,具体定义如下:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
- __builtin_expect是gcc的内置函数,是一个预处理命令(即起作用于编译时预处理阶段),用于代码优化, 具体定义如下:
gcc(version 4.4.0)
long __builtin_expect (long exp, long c)
__builtin_expect的作用
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
- 直白点说:__builtin_expect的作用是告知编译器预期表达式exp等于c的可能性更大,编译器可以根据该因素更好的对代码进行优化。
likely与unlikely的作用
- 从以上__builtin_expect的作用我们可以理解likely与unlikely的作用,就是表达性x为真的可能性更大(likely)和更小(unlikely)。
使用
- 在内核代码以及标准库等底层代码中时常可以看到它们的使用,如下:
if (likely(...)){
}
if (unlikely(...)){
}
注意
- likely与unlikely生效,需要指定一定的优化等级,优化等级为0(即-O0)时,它们无作用。
- 如果likely和unlikely的使用不符合真实情况,代码的执行效率更低。
原理 - 该宏如何起到优化作用
- CPU在执行指令时采用的是流水线的方式,一条指令的执行大致会经过"取码 --> 译码 -->执行",如果在执行时发现需要进行跳转的话,会flush流水线,然后从新的地址重新开始"取码 --> 译码 --> 执行",这个过程会降低代码的执行效率,所以尽量减少跳转的可能(也就是flush流水线的发生频率),就可以提高代码的执行效率。
- 以上是别人博客中摘录的,我补充下:发生跳转的原因是需要执行别的代码块,例如:调用函数,if语句等;代码块的跳转需要执行保存当前操作和数据等压栈出栈操作,会浪费一定的性能。
- likely与unlikely是减小if语句造成的代码跳转。
likely与unlikely是如何减小跳转频率的呢?
if (xxx){
A;
} else {
B;
}
c;
- 在if语句中,如以上代码,从C/C++语言角度去理解,用户可能会认为有两个代码块({}),但是实际上只有一个代码块,查看其汇编代码可知:以上语句从汇编角度去理解类似于:
if (xxx){
A;
goto c; //如果没有c,就直接return了,如果有后续代码c,则跳转回c处运行。
}
B;
c;
- 这样,如果是执行else分支(即执行代码B),程序运行并不会发生代码跳转,执行效率更高,likely与unlikely的作用就是调整A和B的位置,将概率更高的分支放在B处,概率低的分支放在A处。
- 编码时手工也能实现,如下:
* value 值大于0的概率更大,则如下代码
if (value > 0){
A;
} else {
B;
}
或者:
if (value <= 0){
B;
} else {
A;
}
- likely和unlikely只是将该优化手段具体化,由编译器自动调整代码位置。