分支预测以及 gcc 的 __builtin_expect

在阅读代码的时候,发现有类似这种写法:

bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx);
if (unlikely(!bvl)) {
  mempool_free(bio, bio_pool);
  bio = NULL;
  goto out;
}

注意到第二行,if 的判断中,加入了一个 unlikely,这个东西是一个 CPU 指令的优化,用于分支预判,关于分支预判,可以看这个 stackoverflow 的经典问答,http://stackoverflow.com/quest…,简单的说,就是 CPU 由于指令流水,需要在上一个条件判断的指令尚未得到结果的时候,从代码段中预读下一个指令,这个时候,会使用一个分支预测的东西,猜一个分支,然后把指令先读出来

举例说明:假如我们有如下的代码:

if (do_judge())
{
    func_a();
}
else
{
    func_b();
}

CPU 一边读取指令,一边执行指令,同时进行,那么当指令的执行模块在跑 do_judge() 这个判断的时候,读取的模块怎么办呢,一种方法是停下来,等 do_judge() 跑完,知道结果了,再根据 true or false 去读取对应分支下面的代码指令,但是这样就要干等,会浪费好几个 CPU 时钟周期,另外一种方式,就是猜,我先猜一个结果,反正不是 true 就是 false 嘛,猜一个然后先把对应的指令读出来,准备好给执行模块用。显然,如果猜中了,那么皆大欢喜,CPU 可以继续高速运转,如果不幸猜错了,那么就悲剧了,需要刚刚的这几步全部退回去重来,重新去读取另外一个分支下面的代码。自然效率就会大打折扣了。

回到之前的 likely,这个东西其实是一个宏定义,定义如下

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

__builtin_expect 是一个 gcc 的内置函数,那么这个写法的用意就很好理解了,程序员根据经验,显式的指明当前 if 分支一般情况下是否会成立,好加大 CPU 分支预测的成功率,加快代码的执行效率,不过,另外我们注意到,定义中有这个写法

!!(x)

这种两次取反的写法,作用是把非零的值,全部转到 1 上面去,也就说 0 两次取反后还是 0,而其他的非零值,包括大于 1 的整形,布尔型,等等,都会转成 1,好跟 __builtin_expect 后面的第二个参数比较。更多资料,可以进一步阅读,http://kernelnewbies.org/FAQ/L…http://stackoverflow.com/quest…

Leave a Reply

Your email address will not be published. Required fields are marked *