当前位置: 首页 > news >正文

Linux 中的 likely 和 unlikely

1. 源码

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

实际上就是通过GCC 的内建函数 __builtin_expect() 进行编译优化:

long __builtin_expect (long exp, long c)

该函数是告诉编译器:参数exp 为c 的可能更大,编译器可能就会根据这个提示信息,做一些分支预测上的代码优化。

参数 c,与函数的返回值无关,无论 c 为何值,函数的返回值都是 exp

例如:

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

这里更希望不执行 foo函数,因为我们期盼x 表达式的值为 0。

1.2 __builtin_expect注意

  • 参数c 与函数的返回值无关,无论 c 为何值,函数的返回值都为 exp;
  • exp 是一个完整的表达式,返回值是该表达式的值;
  • 编译时需要使用编译选项 -fprofile-arcs

1.3 likely 和unlikely 中的!!(x)

从源码中我们看到 likyly 和unlikely 就是使用了GCC 的内建函数 __builtin_expect,但exp 是!!(x),通过两次取反实现将表达式 x 变成bool,然后与 1 和 0 作比较,告诉编译器 x 为真或为假的可能性很高。

例如:

!!(100)

第一次取反值为0,第二次取反后为 1。实际就是为了返回100 的bool 值为 true

同样:

!!(-100)

第一次取反值为0,第二次取反后为 1。实际就是为了返回-100 的bool 值为 true

2. 验证 likeyly()

#include <stdio.h>
#include <stdlib.h>
 
#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)
 
int main(int argc,  char** argv)
{
    int a = atoi(argv[1]);
 
    if (likely(a > 0)) {
        a = 0x123;
    } else {
        a = 0x456;
    }
 
    printf("a= 0x%x\n", a);
 
    return 0;
}

该 demo 告诉编译器 a > 0 的可能性更大。

下面通过汇编的代码来看下编译器的分析预测:

$ gcc -fprofile-arcs -O2 -c test.c
$ objdump -S test.o


0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64
   4:   48 83 ec 08             sub    $0x8,%rsp
   8:   48 8b 7e 08             mov    0x8(%rsi),%rdi
   c:   31 f6                   xor    %esi,%esi
   e:   ba 0a 00 00 00          mov    $0xa,%edx
  13:   48 83 05 00 00 00 00    addq   $0x1,0x0(%rip)        # 1b <main+0x1b>
  1a:   01
  1b:   e8 00 00 00 00          call   20 <main+0x20>
  20:   48 83 05 00 00 00 00    addq   $0x1,0x0(%rip)        # 28 <main+0x28>
  27:   01
  28:   ba 23 01 00 00          mov    $0x123,%edx     //编译器直接下给a赋值0x123
  2d:   85 c0                   test   %eax,%eax       //先赋值,再来判断 a>0
  2f:   7e 22                   jle    53 <main+0x53>  //使用jle指令判断当a<=0,跳转
  31:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 38 <main+0x38>
  38:   bf 02 00 00 00          mov    $0x2,%edi
  3d:   31 c0                   xor    %eax,%eax
  3f:   e8 00 00 00 00          call   44 <main+0x44>
  44:   48 83 05 00 00 00 00    addq   $0x1,0x0(%rip)        # 4c <main+0x4c>
  4b:   01
  4c:   31 c0                   xor    %eax,%eax
  4e:   48 83 c4 08             add    $0x8,%rsp
  52:   c3                      ret
  53:   48 83 05 00 00 00 00    addq   $0x1,0x0(%rip)        # 5b <main+0x5b>
  5a:   01
  5b:   ba 56 04 00 00          mov    $0x456,%edx      //赋值0x456的分支在最后
  60:   eb cf                   jmp    31 <main+0x31>
  62:   66 66 2e 0f 1f 84 00    data16 cs nopw 0x0(%rax,%rax,1)
  69:   00 00 00 00
  6d:   0f 1f 00                nopl   (%rax)

编译器通过likely() 得知 a>0 的概率很大,直接先给 a 赋值0x123 了,如果不满足条件会进行跳转,将 a = 0x456 的代码分支放到了最后。

3. 验证 unlikely()

#include <stdio.h>
#include <stdlib.h>
 
#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)
 
int main(int argc,  char** argv)
{
    int a = atoi(argv[1]);
 
    if (unlikely(a > 0)) {
        a = 0x123;
    } else {
        a = 0x456;
    }
 
    printf("a= 0x%x\n", a);
 
    return 0;
}

该 demo 告诉编译器 a > 0 的可能性很小,或不希望 a>0.

下面通过汇编的代码来看下编译器的分析预测:

$ gcc -fprofile-arcs -O2 -c test.c
$ objdump -S test.o


0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64
   4:   48 83 ec 08             sub    $0x8,%rsp
   8:   48 8b 7e 08             mov    0x8(%rsi),%rdi
   c:   31 f6                   xor    %esi,%esi
   e:   ba 0a 00 00 00          mov    $0xa,%edx
  13:   48 83 05 00 00 00 00    addq   $0x1,0x0(%rip)        # 1b <main+0x1b>
  1a:   01
  1b:   e8 00 00 00 00          call   20 <main+0x20>
  20:   85 c0                   test   %eax,%eax         //面对概率小,编译器选择先判断a>0
  22:   7f 2f                   jg     53 <main+0x53>    //使用jg指令,如果a>0,跳转
  24:   48 83 05 00 00 00 00    addq   $0x1,0x0(%rip)        # 2c <main+0x2c>
  2b:   01
  2c:   ba 56 04 00 00          mov    $0x456,%edx
  31:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 38 <main+0x38>
  38:   bf 02 00 00 00          mov    $0x2,%edi
  3d:   31 c0                   xor    %eax,%eax
  3f:   e8 00 00 00 00          call   44 <main+0x44>
  44:   48 83 05 00 00 00 00    addq   $0x1,0x0(%rip)        # 4c <main+0x4c>
  4b:   01
  4c:   31 c0                   xor    %eax,%eax
  4e:   48 83 c4 08             add    $0x8,%rsp
  52:   c3                      ret
  53:   48 83 05 00 00 00 00    addq   $0x1,0x0(%rip)        # 5b <main+0x5b>
  5a:   01
  5b:   ba 23 01 00 00          mov    $0x123,%edx
  60:   eb cf                   jmp    31 <main+0x31>
  62:   66 66 2e 0f 1f 84 00    data16 cs nopw 0x0(%rax,%rax,1)
  69:   00 00 00 00
  6d:   0f 1f 00                nopl   (%rax)

4. 总结

  • likely()、unlikely() 原理是利用了GCC 的内建函数 __builtin_expect(),通过编译器预测代码分支;
  • GCC 编译器会将不希望的代码分支放最后;
  • GCC 可能优先执行期盼的执行,再进行判断,不同的编译器版本实现方式不同;
  • likely() 表示该表达式为 “True” 的概率大一些,unlikely() 表示改表达式为 “True” 的概率小一些;
  • likely()、unlikely() 通过分支预测指令的预取能提高代码的执行效率。 但是前提在使用的过程当中程序的开发者必须对自己的代码逻辑有清晰的认识,知道什么样的逻辑会大概率执行,什么样的逻辑大概率不会执行,只有这样才能通likely,unlikely 的判断做精准的分支预测,提高程序的运行性能。

更多的GCC 内建函数可以查看:GCC 内建函数

相关文章:

  • Docker安装mysql——Linux系统
  • 安卓屏保调试
  • 五子棋小游戏-简单开发版
  • 【数据分析】读取文件
  • 部署 T-Pot:构建高级威胁捕获与分析平台的精妙指南
  • DNS服务和实验
  • uniapp 多环境配置打包,比较优雅的解决方案,全网相对优解
  • 自动化立体仓库堆垛机HMI屏幕程序施耐德HMIGXU系列 Vijeo Designer功能设计
  • 【为什么游戏能使人上瘾】
  • Windows安全日志Defender 的配置被修改5007
  • 《灵珠觉醒:从零到算法金仙的C++修炼》卷三·天劫试炼(53)炼妖壶收子集 - 子集问题(位运算与回溯)
  • transformer入门详解
  • 设计模式之原型模式:原理、实现与应用
  • python习题卷1
  • 【从零开始学习计算机科学】算法分析(一)算法、渐进分析、递归分析
  • JAVA实战开源项目:教学辅助平台(Vue+SpringBoot) 附源码
  • Word:双栏排版操作步骤及注意事项
  • 3个 Vue $set 的应用场景
  • 查询MySQL表占用磁盘大小的方法
  • 重生之我在学Vue--第14天 Vue 3 国际化(i18n)实战指南
  • 戛纳参赛片《爱丁顿》评论两极,导演:在这个世道不奇怪
  • 种植耐旱作物、启动备用水源,甘肃各地多举措应对旱情
  • 国际博物馆日|航海博物馆:穿梭于海洋神话与造船工艺间
  • 知名中医讲师邵学军逝世,终年51岁
  • 美国考虑让移民上真人秀竞逐公民权,制片人称非现实版《饥饿游戏》
  • 有关“普泽会”,俄官方表示:有可能