目录

1.引言

2. 函数声明

2.1. 功能描述

2.2. 参数详解

  ① exp

   ② c

2.3. 返回值

2.4. 使用方法

3. RATIONALE(原理)

4. likely()和unlikely()


1.引言

在很多源码如Linux内核、Glib等,我们都能看到likely()和unlikely()这两个宏,通常这两个宏定义是下面这样的形式。

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

可以看出这2个宏都是使用函数 __builtin_expect()实现的, __builtin_expect()函数是GCC的一个内建函数(build-in function).

2. 函数声明

函数__builtin_expect()是GCC v2.96版本引入的, 其声明如下:
long __builtin_expect(long exp, long c);

2.1. 功能描述

由于大部分程序员在分支预测方面做得很糟糕,所以GCC 提供了这个内建函数来帮助程序员处理分支预测.

你期望 exp 表达式的值等于常量 c, 看 c 的值, 如果 c 的值为0(即期望的函数返回值), 那么 执行 if 分支的的可能性小, 否则执行 else 分支的可能性小(函数的返回值等于第一个参数 exp).

GCC在编译过程中,会将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降, 达到优化程序的目的.

通常,你也许会更喜欢使用 gcc 的一个参数 '-fprofile-arcs' 来收集程序运行的关于执行流程和分支走向的实际反馈信息,但是对于很多程序来说,数据是很难收集的。

2.2. 参数详解

  ① exp

    exp 为一个整型表达式, 例如: (ptr != NULL)

   ② c

     c 必须是一个编译期常量, 不能使用变量

2.3. 返回值

  返回值等于 第一个参数 exp

2.4. 使用方法

与关键字if一起使用.首先要明确一点就是 if (value) 等价于 if (__builtin_expert(value, x)), 与x的值无关.

例子如下:

例子1 : 期望 x == 0, 所以执行func()的可能性小

if (__builtin_expect(x, 0))
{func();
}
else
{//do someting
}

例子2 : 期望 ptr !=NULL这个条件成立(1), 所以执行func()的可能性小

if (__builtin_expect(ptr != NULL, 1))
{  //do something
}
else
{func();
} 

例子3 : 引言中的likely()和unlikely()宏

  首先,看第一个参数!!(x), 他的作用是把(x)转变成"布尔值", 无论(x)的值是多少 !(x)得到的是true或false, !!(x)就得到了原值的"布尔值"

  使用 likely() ,执行 if 后面的语句 的机会更大,使用 unlikely(),执行 else 后面的语句的机会更大。

#define likely(x)    __builtin_expect(!!(x), 1)
#define unlikely(x)  __builtin_expect(!!(x), 0)int main(char *argv[], int argc)
{int a;/* Get the value from somewhere GCC can't optimize */a = atoi (argv[1]);if (unlikely (a == 2)){a++;}else{a--;}printf ("%d\n", a);return 0;
}

3. RATIONALE(原理)

if else 句型编译后, 一个分支的汇编代码紧随前面的代码,而另一个分支的汇编代码需要使用JMP指令才能访问到.

很明显通过JMP访问需要更多的时间, 在复杂的程序中,有很多的if else句型,又或者是一个有if else句型的库函数,每秒钟被调用几万次,

通常程序员在分支预测方面做得很糟糕, 编译器又不能精准的预测每一个分支,这时JMP产生的时间浪费就会很大,

函数__builtin_expert()就是用来解决这个问题的.

具体从汇编角度来分析其原理的例子,大家可以参照http://kernelnewbies.org/FAQ/LikelyUnlikely,

其对应的中文翻译版见http://velep.com/archives/795.html

本文讲的likely()和unlikely()两个宏,在linux内核代码和一些应用中可常见到它们的身影。实质上,这两个宏是关于GCC编译器内置宏__builtin_expect的使用。

顾名思义,likely()指“很有可能”之意,而unlikely()指“不太可能”之意。那么,在实际应用中,它们代表什么?又是怎么使用的呢?下面是一篇外文翻译(加上了本人的一些理解),给出了详细答案。

4. likely()和unlikely()

对于linux内核代码,在条件判断语句中经常看到likely()和unlikely()的调用,如下代码所示:

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx);
if (unlikely(!bvl))
{mempool_free(bio, bio_pool);bio = NULL;goto out;}

在这里,调用likely()或unlikely()告诉编译器这个条件很有可能或者不太有可能发生,好让编译器对这个条件判断进行正确地优化。这两个宏在include/linux/compiler.h文件中可以找到:

在GCC文档中可找到上述代码中__builtin_expect的说明,摘录如下:

-- Built-in Function: long __builtin_expect (long EXP, long C)You may use `__builtin_expect' to provide the compiler with branchprediction information.  In general, you should prefer to useactual profile feedback for this (`-fprofile-arcs'), asprogrammers are notoriously bad at predicting how their programsactually perform.  However, there are applications in which thisdata is hard to collect.The return value is the value of EXP, which should be an integralexpression.  The value of C must be a compile-time constant.  Thesemantics of the built-in are that it is expected that EXP == C.For example:if (__builtin_expect (x, 0))foo ();would indicate that we do not expect to call `foo', since weexpect `x' to be zero.  Since you are limited to integralexpressions for EXP, you should use constructions such asif (__builtin_expect (ptr != NULL, 1))error ();when testing pointer or floating-point values.

__builtin_expect说明中给出了两示例:

if (__builtin_expect (x, 0)) foo ();

表示期望x == 0,也就是不期望不执行foo()函数;

同理,if (__builtin_expect (ptr != NULL, 1)) error ();

表示期望指针prt非空,也就是不期望看到error()函数的执行。

编译器做的优化工作

从GCC的说明中可知,__builtin_expect的主要作用就是:帮助编译器判断条件跳转的预期值,避免因执行jmp跳转指令造成时间浪费。那么它是怎么帮助编译器进行优化的呢?

编译器优化时,根据条件跳转的预期值,按正确地顺序生成汇编代码,把“很有可能发生”的条件分支放在顺序执行指令段,而不是jmp指令段(jmp指令会打乱CPU的指令执行顺序,大大影响CPU指令执行效率)。

下面举例说明。下面这个简单的C程序使用gcc -O2进行编译。

#define likely(x)    __builtin_expect(!!(x), 1)
#define unlikely(x)  __builtin_expect(!!(x), 0)int main(char *argv[], int argc)
{int a;/* 获取输入参数值(编译器不能进行优化) */a = atoi (argv[1]);if (unlikely (a == 2))a++;elsea--;printf ("%d\n", a);return 0;
}

使用objdump -S反汇编,查看它的汇编代码。

080483b0 <main>://             开头80483b0:       55                      push   %ebp80483b1:       89 e5                   mov    %esp,%ebp80483b3:       50                      push   %eax80483b4:       50                      push   %eax80483b5:       83 e4 f0                and    $0xfffffff0,%esp//             调用atoi()80483b8:       8b 45 08                mov    0x8(%ebp),%eax80483bb:       83 ec 1c                sub    $0x1c,%esp80483be:       8b 48 04                mov    0x4(%eax),%ecx80483c1:       51                      push   %ecx80483c2:       e8 1d ff ff ff          call   80482e4 <atoi@plt>80483c7:       83 c4 10                add    $0x10,%esp//             把输入值与2进行比较,即执行:“a == 2”80483ca:       83 f8 02                cmp    $0x2,%eax//             --------------------------------------------------------//             如果'a' 等于 2 (程序里面认为不太可能), 则跳转,//             否则继续执行, 从而不破坏CPU的指令执行顺序.//             --------------------------------------------------------80483cd:       74 12                   je     80483e1 <main+0x31>80483cf:       48                      dec    %eax//             调用printf80483d0:       52                      push   %edx80483d1:       52                      push   %edx80483d2:       50                      push   %eax80483d3:       68 c8 84 04 08          push   $0x80484c880483d8:       e8 f7 fe ff ff          call   80482d4 <printf@plt>//             返回0并退出.80483dd:       31 c0                   xor    %eax,%eax80483df:       c9                      leave80483e0:       c3                      ret

在上面程序中,用likely()代替其中的unlikely(),重新编译,再来看它的汇编代码:

080483b0 <main>://             开头80483b0:       55                      push   %ebp80483b1:       89 e5                   mov    %esp,%ebp80483b3:       50                      push   %eax80483b4:       50                      push   %eax80483b5:       83 e4 f0                and    $0xfffffff0,%esp//             调用atoi()80483b8:       8b 45 08                mov    0x8(%ebp),%eax80483bb:       83 ec 1c                sub    $0x1c,%esp80483be:       8b 48 04                mov    0x4(%eax),%ecx80483c1:       51                      push   %ecx80483c2:       e8 1d ff ff ff          call   80482e4 <atoi@plt>80483c7:       83 c4 10                add    $0x10,%esp//             --------------------------------------------------//             如果'a' 等于 2 (程序认为很有可能), 则不跳转,继续执行,//             这样就不破坏CPU的指令执行顺序. //             只有当 a != 2 时才会发生跳转, 而这种情况,程序认为是不太可能的.//             ---------------------------------------------------80483ca:       83 f8 02                cmp    $0x2,%eax80483cd:       75 13                   jne    80483e2 <main+0x32>//             a++ 指令的优化80483cf:       b0 03                   mov    $0x3,%al//             调用printf()80483d1:       52                      push   %edx80483d2:       52                      push   %edx80483d3:       50                      push   %eax80483d4:       68 c8 84 04 08          push   $0x80484c880483d9:       e8 f6 fe ff ff          call   80482d4 <printf@plt>//             返回0并退出.80483de:       31 c0                   xor    %eax,%eax80483e0:       c9                      leave80483e1:       c3                      ret

如何使用?

在一个条件判断语句中,当这个条件被认为是非常非常有可能满足时,则使用likely()宏,否则,条件非常非常不可能或很难满足时,则使用unlikely()宏。

参考资料

本文英文原文:http://kernelnewbies.org/FAQ/LikelyUnlikely

更多GCC内置宏或函数,详见:http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

likely(x)与unlikely(x)函数,即__builtin_expect的使用相关推荐

  1. 解析Linux中的 likely 和 unlikely

    Linux 中多处出现 likely 和 unlikely 的使用,它们的定义如下: # define likely(x) __builtin_expect(!!(x), 1) # define un ...

  2. 内核的likely和unlikely

    1. 概念 指令周期是指执行一条指令所需要的时间,一般由若干个机器周期组成,是从取指令.分析指令到指令执行完所需的全部. 预取指令具体方法就是在不命中时,当数据从主存储器中取出送往CPU的同时,把主存 ...

  3. __attribute__函数的作用

    [iOS]__attribute__ 标签: iOS 2016-09-07 19:41 107人阅读 评论(0) 收藏 举报  分类: iOS开发(52)  版权声明:本文为博主原创文章,未经博主允许 ...

  4. linux下的系统调用函数到内核函数的追踪

    Original from: http://blog.chinaunix.net/uid-28458801-id-3468966.html 使用的 glibc : glibc-2.17 使用的 lin ...

  5. c# 定位内存快速增长_CTF丨Linux Pwn入门教程:针对函数重定位流程的相关测试(下)...

    Linux Pwn入门教程系列分享已到尾声,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  6. GCC __builtin_expect与kernel指令序列优化

    例题描述 例题描述:通过C语言识别一个int型数据在十进制下是否为回文数字.不能有额外的字符串空间开销. 如:2156512是回文数,而21565不是回文数. 问题分析: 1. 当这个数字是负数的时候 ...

  7. __builtin_expect详解

    在GTK+2.0源码中有很多这样的宏:G_LIKELY和G_UNLIKELY.比如下面这段代码: if (G_LIKELY (acat == 1)) /* allocate through magaz ...

  8. POSIX 线程清理函数

    POSIX 多线程的 cleanup 函数 控制清理函数的函数有两个,一个是 pthread_cleanup_push(), 用来把清理函数压入栈中,另一个是 pthread_cleanup_pop( ...

  9. 揭秘Facebook官方底层C++函数Folly

    2019独角兽企业重金招聘Python工程师标准>>> Folly与Boost.当然还有std等组件库的关系是互为补充,而不是彼此竞争.实际上,只有当我们需要的东西既没有,也无法满足 ...

最新文章

  1. 机器学习与数据挖掘有什么异同?
  2. 和与余数的和同余理解_每日一题 | 第38期:数量关系之余数特性
  3. Ubuntu 建立tftp服务器
  4. map reduce相关程序
  5. 华硕笔记本r414u怎么安装键盘_华硕笔记本键盘灯怎么开
  6. Canvas -画图 关键字
  7. [解题报告]12289 - One-Two-Three
  8. 谈谈出入React框架踩过的坑
  9. 华为P40手机点位图PCBDOC下载
  10. 应用软件与系统不兼容的解决办法,仅供参考
  11. 编译QT项目出现错误:error C2144: syntax error : 'void' should be preceded by ';'
  12. Oraclealterindexrebuild与ORA08104说明
  13. math sub Java_java初学减法运算
  14. I2C总线时序以及ACK和NACK(NAK),SCL被从机拉低?
  15. 2023最新软件测试学习思维导图(从小白到大师进阶之路)
  16. 58%数据泄漏由内部引起,防泄密系统助力企业数据安全管理防泄露
  17. 【状语从句练习题】because / because of / although / in spite of
  18. dede织梦系统接入熊掌号推送api,完整详细教程
  19. iPad Mini 到底什么样?4点概括
  20. 【渝粤教育】电大中专职业健康与安全 (2)作业 题库

热门文章

  1. 菜鸡博客开……开……开源了!
  2. 爱尔兰DPC针对Facebook 5.33亿用户数据泄露事件开展调查
  3. VFS - 代码生成器预览功能实现
  4. MySQL中,关联查询的3种写法…
  5. python3 自定义排序函数_Python自定义排序函数
  6. 如何将u-boot和Linux内核移植到ADSP-SC589上
  7. leetcode589.N叉树的前序遍历C++
  8. c语言ecit,[转载]c# linq的一些运用 – EcitGis – 博客园
  9. wdcdn多节点分布式CDN系统1.3发布
  10. 在加入域时又出现了“不能访问网络位置”的错误 的解决