在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. Maven、SecureCRT使用问题汇集

    1 Maven 无法下载pom文件中相关的依赖包 该问题可能有很多原因,我的原因是host中的localhost被修改了,改回来即可! 看起来好像出了一些网络原因的问题,顺着这个方向搜索,发现国外也有 ...

  2. VBA笔记(一)

    开启VBA编程环境--VBE 方法一:按<Alt+F11>组合建 方法二:查看代码 宏设置 当然启用宏的设置方式不同,宏的启动方式也不一样. 首先打开"office 按钮&quo ...

  3. jvm死锁解决

    那我们怎么确定一定是死锁呢?有两种方法. 1>使用JDK给我们的的工具JConsole,可以通过打开cmd然后输入jconsole打开. 1)连接到需要查看的进程.

  4. 如果asp.net mvc中某个action被执行了两次,请检查是不是以下的原因

    注释 <link rel="icon" href="#"> 这一句后试试

  5. [译]Android调整图像大小的一些方法

    翻译自 某大神在Stack Overflow里的自问自答 (一般我们将Bitmap翻译为位图,但为了更好理解,在本文中我将它翻译成图像): 我们在开发的时候,经常需要从服务器中加载图像到客户端中,但有 ...

  6. jQuery解析AJAX返回的html数据时碰到的问题与解决

    $.ajax({ type : "post", url : "<%=request.getContextPath()%>/ce/articledetail/m ...

  7. MQTT(二)推送

    MQTT V3.1----publish解读 - leeying - 博客园 http://www.cnblogs.com/leeying/p/3791341.html MQTT - 聂永的博客 - ...

  8. opendrive

    opendrive和其他许多网盘一样.注册拥有5G的免费空间.每天1G的免费外链流量.更重要的是,他能够给你提供一个直接外链!这是国内外许多网盘都没有的.当你上载了一个MP3,你想用直接外链的形式在博 ...

  9. 解决linux系统启动之:unexpected inconsistency:RUN fsck

    现象: 虚拟机在启动过程中提示: unexpected inconsistency;RUN fsck MANUALLY 原因分析: 1.由于意外关机导致的文件系统问题 解决方法: 方法1: 输入ROO ...

  10. Android杂记:genymotion与eclipse报错问题

    用eclipse启动genymotion时有时候会报 The connection to adb is down, and a severe error has occured. You must r ...