在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. Python基础知识(一)

  2. 微信"附近的人"新增商家公众号入驻功能

    微信近日升级了“附近的人”,新增商家公众号(认证的服务号和有卡券功能的公众号)可自入驻,这是微信在推出卡券和微信wifi功能后,又一加强连接线下商户能力的功能. 微信在“附近的人”中 增加搜索商户功能 ...

  3. 【转】搞不清FastCgi与php-fpm之间是个什么样的关系?

    我在网上查fastcgi与php-fpm的关系,查了快一周了,基本看了个遍,真是众说纷纭,没一个权威性的定义. 网上有的说,fastcgi是一个协议,php-fpm实现了这个协议: 有的说,php-f ...

  4. class Solution(object): def fizzBuzz(self, n): a = [] i = 1 while(i <= n): if(i%15 == 0): a.append("FizzBuzz") elifleetcode day_01

    412. Fizz Buzz Write a program that outputs the string representation of numbers from 1 to n. But fo ...

  5. 介绍一个非常好用的跨平台C++开源框架:openFrameworks

    介绍一个非常好用的跨平台C++开源框架:openFrameworks 简介 首先需要说明的一点是: openFrameworks 设计的初衷不是为计算机专业人士准备的, 而是为艺术专业人士准备的, 就 ...

  6. Android模拟位置信息

    Android模拟位置程序,俗称GPS欺骗,只能修改采用GPS定位的软件. 手机定位方式目前有4种:基站定位,WIFI定位,GPS定位,AGPS定位 常见的修改手法: 1. 抓包欺骗法,抓包改包欺骗服 ...

  7. Types of CQRS

    Types of CQRS By Vladimir Khorikov CQRS is a pretty defined concept. Often, people say that you eith ...

  8. ACM/ICPC 之 混合图的欧拉回路判定-网络流(POJ1637)

    //网络流判定混合图欧拉回路 //通过网络流使得各点的出入度相同则possible,否则impossible //残留网络的权值为可改变方向的次数,即n个双向边则有n次 //Time:157Ms Me ...

  9. poj2492_A Bug's Life_并查集

    A Bug's Life Time Limit: 10000MS   Memory Limit: 65536K Total Submissions: 34947   Accepted: 11459 D ...

  10. php curl 例子

    get方式: set_time_limit(0);        $url ='http://xxxxxxxx?pwd=wJ2312&s=1&e=2&d=1&t=asc ...