在linux内核源码或一些比较成熟的c语言架构源码中,我们常会见到类似下面的代码:

 if (unlikely(!packet)) {
return res_failed;
} // OR if (likely(packet->type = HTTP)) {
do_something();
} 有的地方可能会用大写,LIKELY() / UNLIKELY(),意思一样。

然后我们看一下unlikely和likely的定义,大部分都是类似如下的宏定义:

 #define likely(x)   __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0) GCC 中用的是 _G_BOOLEAN_EXPR(expr) 来代替 !!(expr), 意思一样,都是把expr或x转成相应布尔变量。

两个定义无一例外调用了一个内置函数 __builtin_expect(bool expr,  int x)。

先解释一下:  LIKELY 和 UNLIKELY 不会对原expr的布尔值产生任何影响,也就是说只要expr == true, LIKELY(expr) 与 UNLIKELY(expr) 都为 true。他们起的只是编译器优化作用。

我们先测试一段代码:

 /**
* @author Lhfcws
* @file test__builtin_expect.c
* @time 2013-07-22
**/ #define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0) int test_likely(int x) {
if(LIKELY(x))
x = 0x00;
else
x = 0xff; return x;
} int test_unlikely(int x) {
if(UNLIKELY(x))
x = 0x00;
else
x = 0xff; return x;
} int test_justif(int x) {
if(x)
x = 0x00;
else
x = 0xff; return x;
}

可见,三个函数唯一的区别就是 if (x) 那里。

我们执行一下命令编译和反汇编(要用 __builtin_expect 的话  -fprofile-arcs 必须加):

gcc -fprofile-arcs -c test__builtin_expect.c -o test__builtin_expect.o
objdump -d test__builtin_expect > builtin.asm

此时打开生成的asm文件,就可以看到上面代码由gcc编译生成的汇编代码。我们截取那三个函数的汇编源码查看。

  <test_likely>:
: push %ebp
: e5 mov %esp,%ebp
: 7d cmpl $0x0,0x8(%ebp)
: 0f c0 setne %al
a: 0f b6 c0 movzbl %al,%eax
d: c0 test %eax,%eax
f: je 1a <test_likely+0x1a>
: c7 movl $0x0,0x8(%ebp)
: eb jmp 3d <test_likely+0x3d>
1a: c7 ff movl $0xff,0x8(%ebp)
: a1 mov 0x20,%eax
: 8b mov 0x24,%edx
2c: c0 add $0x1,%eax
2f: d2 adc $0x0,%edx
: a3 mov %eax,0x20
: mov %edx,0x24
3d: 8b 4d mov 0x8(%ebp),%ecx
: a1 mov 0x28,%eax
: 8b 2c mov 0x2c,%edx
4b: c0 add $0x1,%eax
4e: d2 adc $0x0,%edx
: a3 mov %eax,0x28
: 2c mov %edx,0x2c
5c: c8 mov %ecx,%eax
5e: 5d pop %ebp
5f: c3 ret <test_unlikely>:
: push %ebp
: e5 mov %esp,%ebp
: 7d cmpl $0x0,0x8(%ebp)
: 0f c0 setne %al
6a: 0f b6 c0 movzbl %al,%eax
6d: c0 test %eax,%eax
6f: je 7a <test_unlikely+0x1a>
: c7 movl $0x0,0x8(%ebp)
: eb jmp 9d <test_unlikely+0x3d>
7a: c7 ff movl $0xff,0x8(%ebp)
: a1 mov 0x10,%eax
: 8b mov 0x14,%edx
8c: c0 add $0x1,%eax
8f: d2 adc $0x0,%edx
: a3 mov %eax,0x10
: mov %edx,0x14
9d: 8b 4d mov 0x8(%ebp),%ecx
a0: a1 mov 0x18,%eax
a5: 8b 1c mov 0x1c,%edx
ab: c0 add $0x1,%eax
ae: d2 adc $0x0,%edx
b1: a3 mov %eax,0x18
b6: 1c mov %edx,0x1c
bc: c8 mov %ecx,%eax
be: 5d pop %ebp
bf: c3 ret 000000c0 <test_justif>:
c0: push %ebp
c1: e5 mov %esp,%ebp
c3: 7d cmpl $0x0,0x8(%ebp)
c7: je d2 <test_justif+0x12>
c9: c7 movl $0x0,0x8(%ebp)
d0: eb jmp f5 <test_justif+0x35>
d2: c7 ff movl $0xff,0x8(%ebp)
d9: a1 mov 0x0,%eax
de: 8b mov 0x4,%edx
e4: c0 add $0x1,%eax
e7: d2 adc $0x0,%edx
ea: a3 mov %eax,0x0
ef: mov %edx,0x4
f5: 8b 4d mov 0x8(%ebp),%ecx
f8: a1 mov 0x8,%eax
fd: 8b 0c mov 0xc,%edx
: c0 add $0x1,%eax
: d2 adc $0x0,%edx
: a3 mov %eax,0x8
10e: 0c mov %edx,0xc
: c8 mov %ecx,%eax
: 5d pop %ebp
: c3 ret

如上,我们看到,貌似test_likely 和 test_unlikely 没什么区别, test_justif就是少了setne al开始的三行代码而已(实际上是执行__builtin_expect(!!(x), 1)的代码)。

其实这证明了一件事: LIKELY 和 UNLIKELY 的调用不会影响最终结果,实际两者的结果是一样的。

我们之前提到他们起的作用是优化,因此我们编译的时候加上优化指令。

gcc -O2 -fprofile-arcs -c test__builtin_expect.c -o test__builtin_expect.o
objdump -d test__builtin_expect > builtin_O2.asm

得到汇编:

  <test_likely>:
: ec sub $0x4,%esp
: 8b mov 0x8(%esp),%eax
: addl $0x1,0x0
e: adcl $0x0,0x4
: c0 test %eax,%eax
: je 1f <test_likely+0x1f>
: c0 xor %eax,%eax
1b: c4 add $0x4,%esp
1e: c3 ret
1f: addl $0x1,0x8
: b8 ff mov $0xff,%eax
2b: 0c adcl $0x0,0xc
: eb e7 jmp 1b <test_likely+0x1b>
: 8d b6 lea 0x0(%esi),%esi
3a: 8d bf lea 0x0(%edi),%edi <test_unlikely>:
: ec sub $0x4,%esp
: 8b mov 0x8(%esp),%edx
: addl $0x1,0x10
4e: adcl $0x0,0x14
: d2 test %edx,%edx
: jne <test_unlikely+0x30>
: addl $0x1,0x18
: b8 ff mov $0xff,%eax
: 1c adcl $0x0,0x1c
6c: c4 add $0x4,%esp
6f: c3 ret
: c0 xor %eax,%eax
: eb f8 jmp 6c <test_unlikely+0x2c>
: 8d b6 lea 0x0(%esi),%esi
7a: 8d bf lea 0x0(%edi),%edi <test_justif>:
: ec sub $0x4,%esp
: 8b 4c mov 0x8(%esp),%ecx
: addl $0x1,0x20
8e: adcl $0x0,0x24
: c0 xor %eax,%eax
: c9 test %ecx,%ecx
: jne ab <test_justif+0x2b>
9b: addl $0x1,0x28
a2: b0 ff mov $0xff,%al
a4: 2c adcl $0x0,0x2c
ab: c4 add $0x4,%esp
ae: c3 ret

现在三个函数就有很明显的不同了。

留意一下每个函数其中的三行代码:

 je ... / jne ...    ; 跳转
xor %eap, %eap      ; 其实是 mov 0x00, %eap,改用 xor 是编译器自己的优化。结果等价。
mov 0xff, %eap      ; x = 0xff

可以看到,likely版本和unlikely版本最大的区别是跳转的不同。

likely版本编译器会认为执行 x == true 的可能性比较大,因此将 x == false 的情况作为分支,减少跳转开销。

同理,unlikely版本编译器会认为执行 x == false 的可能性比较大,因此将 x == true 的情况作为分支,减少跳转开销。

总结:

likely 和 unlikely 的使用实际上是为了分支优化,不影响结果,据传,众多程序员平常很少注意分支优化情况,因此gcc有了这个选项。。。

unlikely 一般适用于(但不仅限于)一些错误检查,比如本文开头示例。likely适用于主分支场景,即根据期望大部分情况都会执行的场景。

likely && unlikely in GCC的更多相关文章

  1. VSCode调试go语言出现:exec: "gcc": executable file not found in %PATH%

    1.问题描述 由于安装VS15 Preview 5,搞的系统由重新安装一次:在用vscdoe编译go语言时,出现以下问题: # odbcexec: "gcc": executabl ...

  2. GCC学习(1)之MinGW使用

    GCC学习(1)之MinGW使用 因为后续打算分享一些有关GCC的使用心得的文章,就把此篇当作一个小预热,依此来了解下使用GNU工具链(gcc.gdb.make等)在脱离IDE的情况下如何开发以及涉及 ...

  3. 使用 GCC 和 GNU Binutils 编写能在 x86 实模式运行的 16 位代码

    不可否认,这次的标题有点长.之所以把标题写得这么详细,主要是为了搜索引擎能够准确地把确实需要了解 GCC 生成 16 位实模式代码方法的朋友带到我的博客.先说一下背景,编写能在 x86 实模式下运行的 ...

  4. [异常解决] How to build a gcc toolchain for nRF51 on linux (very detailed!!!)

    1.Install gcc-arm-none-eabi https://devzone.nordicsemi.com/tutorials/7/This link shows that developm ...

  5. CentOS 6.6 升级GCC G++ (当前最新版本为v6.1.0) (完整)

    ---恢复内容开始--- CentOS 6.6 升级GCC G++ (当前最新GCC/G++版本为v6.1.0) 没有便捷方式, yum update....   yum install 或者 添加y ...

  6. GCC 预处理、编译、汇编、链接..

    1简介 GCC 的意思也只是 GNU C Compiler 而已.经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言:它现在还支持 Ada 语言.C++ 语言.Java 语言.Objective ...

  7. 用gcc进行程序的编译

    在Linux系统上,一个档案能不能被执行看的是有没有可执行的那个权限(x),不过,Linux系统上真正认识的可执行文件其实是二进制文件(binary program),例如/usr/bin/passw ...

  8. gcc/linux内核中likely、unlikely和__attribute__(section(""))属性

    查看linux内核源码,你会发现有很多if (likely(""))...及if (unlikely(""))...语句,这些语句其实是编译器的一种优化方式,具 ...

  9. Ubuntu 14.04 LTS 下升级 gcc 到 gcc-4.9、gcc-5 版本

    如果没记错的话,阿里云ECS上的Ubuntu也是LTS版本. 如果还在使用较旧版本的Ubuntu,或者是Ubuntu LTS,那么我们是很难体验新版gcc的.怎么办呢? 我们或许可以自己去编译用旧版本 ...

  10. 低版本GCC程序向高版本移植的兼容性问题

    将低版本gcc编译过的程序移植到高版本GCC时, 可能会出现一些兼容性问题. 原因是, 为了适应新的标准,一些旧的语法规则被废弃了. 关于这方面的一些具体资料可从该处查询. 这里只是自己遇到的其中一个 ...

随机推荐

  1. vi 常用命令

    1.关于退出 :wq!  ----强制保存退出 :wq  ---- 保存退出 ZZ  ---- 作用和:wq一样,(注意Z是大写的,并且不是在命令模式) :q  ---- 退出 :q!  ---  强 ...

  2. Mac Pro 软件安装/个性化配置 汇总

    苹果产品维修 一.Spotlight 搜索程序和文档 Spotlight是最最常用的东西, 类似Windows开始菜单中的搜索.  可以用来搜索文档,也可以搜索本机的程序, 这样可以快速启动. 点击右 ...

  3. js的继承

    js要实现继承有很多方法,个人总结大致分为三种: function people(){ this.specials = "人类"; } function p1(name){ thi ...

  4. 解决 iOS 9.1 微信内置浏览器中html audio 不能自动播放的问题

    使用微信现在提供过的微信js-sdk 在ready中进行播放便可. 首先引用js : <script src="http://res.wx.qq.com/open/js/jweixin ...

  5. linux git安装及配置(包括更新)

    1.在终端运行命令 sudo apt-get install git 2.查看版本号 git --version  (若不是最新可更新 自选) 更新提示: sudo add-apt-repositor ...

  6. selenium常用的js总结

    1. 对input执行输入 直接设置value属性, 此方法主要应对输入框自动补全以及readonly属性的element,sendkeys不稳定 比如: //inputbox is a WebEle ...

  7. ACM/ICPC 之 网络流入门-EK算法(参考模板)(POJ1273)

    基于残留网络与FF算法的改进-EK算法,核心是将一条边的单向残留容量的减少看做反向残留流量的增加. //网络流 //EK算法 //Time:16Ms Memory:348K #include<i ...

  8. jqueyr eq get用法

    相信大部份人都会把这2个的用法搞错.仔细查看下API文档就可以知道.eq返回的是一个jquery对象,get返回的是一个html 对象数组.举个例子: <p style="color: ...

  9. UTC与GMT时间

    整个地球分为二十四时区,每个时区都有自己的本地时间.在国际无线电通信场合,为了统一起见,使用一个统一的时间,称为通用协调时(UTC, Universal Time Coordinated).UTC与格 ...

  10. HTML5本地数据库(SQLite)示例

    本文转载自http://blog.sina.com.cn/s/blog_641cf27f01016pm5.html 按照国内一HTML5先行者的例子仿写了一个用HTML5 API来操作本地SQLite ...