Linux 中的 likely 和 unlikely

news2025/3/17 9:55:15

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 内建函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2316548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS -属性值的计算过程

目录 一、抛出两个问题1.如果我们学过优先级关系&#xff0c;那么请思考如下样式为何会生效2.如果我们学习过继承&#xff0c;那么可以知道color是可以被子元素继承使用的&#xff0c;那么请思考下述情景为何不生效 二、属性值计算过程1.确定声明值2.层叠冲突3.使用继承4.使用默…

百度贴吧IP和ID是什么意思?怎么查看

在百度贴吧这一充满活力的网络社区中&#xff0c;IP和ID是两个频繁出现的概念。它们各自承载着不同的意义和作用&#xff0c;对于贴吧用户而言&#xff0c;了解这两个概念有助于更好地参与社区互动、保护个人隐私以及维护社区秩序。本文将详细解析百度贴吧中IP和ID的含义&#…

SpiderX:专为前端JS加密绕过设计的自动化工具

SpiderX 一、工具概述 SpiderX是一款专为解决前端JS加密问题而设计的自动化绕过工具。在网络安全领域&#xff0c;随着前端加密技术的普及&#xff0c;传统的爬虫和自动化测试工具在面对复杂的JS加密时显得力不从心。SpiderX应运而生&#xff0c;旨在通过自动化手段高效绕过前…

基于银河麒麟系统ARM架构安装达梦数据库并配置主从模式

达梦数据库简要概述 达梦数据库&#xff08;DM Database&#xff09;是一款由武汉达梦公司开发的关系型数据库管理系统&#xff0c;支持多种高可用性和数据同步方案。在主从模式&#xff08;也称为 Master-Slave 或 Primary-Secondary 模式&#xff09;中&#xff0c;主要通过…

【AWS入门】AWS云计算简介

【AWS入门】AWS云计算简介 A Brief Introduction to AWS Cloud Computing By JacksonML 什么是云计算&#xff1f;云计算能干什么&#xff1f;我们如何利用云计算&#xff1f;云计算如何实现&#xff1f; 带着一系列问题&#xff0c;我将做一个普通布道者&#xff0c;引领广…

适合企业内训的AI工具实操培训教程(37页PPT)(文末有下载方式)

详细资料请看本解读文章的最后内容。 资料解读&#xff1a;适合企业内训的 AI 工具实操培训教程 在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术迅速发展&#xff0c;深度融入到各个领域&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;更是成…

【数据结构与算法】Java描述:第四节:二叉树

一、树的相关概念 编程中的树是模仿大自然中的树设计的&#xff0c;呈现倒立的结构&#xff0c;我们着重掌握 二叉树 。 1.1 基本概念&#xff1a; 结点的度&#xff1a;一个结点有几个子结点&#xff0c;度就是几&#xff1b; 如上图&#xff1a;A的度为3 树的度&#xff1…

Day5 结构体、文字显示与GDT/IDT初始化

文章目录 1. harib02b用例&#xff08;使用结构体&#xff09;2. harib02c用例3. harib02d用例&#xff08;显示字符图案&#xff09;3. harib02e用例&#xff08;增加字符图案&#xff09;4. harib02g用例4.1 显示字符串4.2 显示变量值 5. harib02h用例&#xff08;显示鼠标&a…

系统思考全球化落地

感谢加密货币公司Bybit的再次邀请&#xff0c;为全球团队分享系统思考课程&#xff01;虽然大家来自不同国家&#xff0c;线上学习的形式依然让大家充满热情与互动&#xff0c;思维的碰撞不断激发新的灵感。 尽管时间存在挑战&#xff0c;但我看到大家的讨论异常积极&#xff…

【开原宝藏】30天学会CSS - DAY1 第一课

下面提供一个由浅入深、按步骤拆解的示例教程&#xff0c;让你能从零开始&#xff0c;逐步理解并实现带有旋转及悬停动画的社交图标效果。为了更简单明了&#xff0c;以下示例仅创建四个图标&#xff08;Facebook、Twitter、Google、LinkedIn&#xff09;&#xff0c;并在每一步…

钉钉项目报销与金蝶系统高效集成技术解析

钉钉报销【项目报销类】集成到金蝶付款单【画纤骨】的技术实现 在企业日常运营中&#xff0c;数据的高效流转和准确对接是提升业务效率的关键。本文将分享一个具体的系统对接集成案例&#xff1a;如何将钉钉平台上的项目报销数据无缝集成到金蝶云星空的付款单系统中。本次方案…

Datawhale coze-ai-assistant:Task 1 了解 AI 工作流 + Coze的介绍

学习网址&#xff1a;Datawhale-学用 AI,从此开始 工作流&#xff08;Workflow&#xff09;是指完成一项任务或目标时&#xff0c;按照特定顺序进行的一系列活动或步骤。它强调在计算机应用环境下的自动化&#xff0c;通过将复杂的任务拆分成多个简单的步骤&#xff0c;每一步都…

深度学习 Deep Learning 第3章 概率论与信息论

第三章 概率与信息论 概述 本章介绍了概率论和信息论的基本概念及其在人工智能和机器学习中的应用。概率论为处理不确定性提供了数学框架&#xff0c;使我们能够量化不确定性和推导新的不确定陈述。信息论则进一步帮助我们量化概率分布中的不确定性。在人工智能中&#xff0c;…

GStreamer —— 2.15、Windows下Qt加载GStreamer库后运行 - “播放教程 1:Playbin 使用“(附:完整源码)

运行效果 介绍 我们已经使用了这个元素&#xff0c;它能够构建一个完整的播放管道&#xff0c;而无需做太多工作。 本教程介绍如何进一步自定义&#xff0c;以防其默认值不适合我们的特定需求。将学习&#xff1a; • 如何确定文件包含多少个流&#xff0c;以及如何切换 其中。…

MYsql—1

1.mysql的安装 在windows下安装mysql&#xff0c;直接官网搜索即可&#xff1a;http://www.mysql.com/&#xff0c;自己找想要的版本进行download&#xff0c;官网长这样 安装路径需要是英文路径&#xff0c;设置默认即可&#xff0c;若安装执行内容时报错&#xff0c;则AltCt…

位运算(基础算法)

按位与AND&#xff08; & &#xff09; 只有当两个位都为1时&#xff0c;结果才为1,否则为0。结果不会变大 按位或 OR&#xff08; | &#xff09; 只有当两个位中有一个为1时&#xff0c;结果才为1,否则为0。结果不会变小 按位异或 XOR &#xff08; ^ &#xff09; 只…

硬件地址反序?用位操作为LED灯序“纠偏”。反转二进制数即可解决

特别有意思&#xff0c;LED的灯序与其硬件地址刚好相反&#xff0c;没办法直接通过加1实现二进制进位的亮灯操作&#xff0c;查了一些资料说用数组和switch实现&#xff0c;觉得太麻烦了&#xff0c;思索良久&#xff0c;就想到了反转二进制数解决这个问题。 reverse_bits( )是…

图解AUTOSAR_CP_BSWMulticoreLibrary

AUTOSAR BSW 多核库详解 AUTOSAR基础软件多核操作库详细解析 目录 架构概述 1.1. 组件架构 1.2. API结构 1.3. 错误处理流程详细设计 2.1. 基础数据类型 2.2. 接口说明 2.3. 错误处理机制使用指南 3.1. 配置说明 3.2. 典型应用场景 3.3. 注意事项 1. 架构概述 1.1. 组件架构 …

热key探测技术架构设计与实践

参考&#xff1a; 得物热点探测技术架构设计与实践 Redis数据倾斜与JD开源hotkey源码分析揭秘 京东热点检测 HotKey 学习笔记 hotkey: 京东App后台中间件&#xff0c;毫秒级探测热点数据&#xff0c;毫秒级推送至服务器集群内存&#xff0c;大幅降低热key对数据层查询压力 …

【微服务】java中http调用组件深入实战详解

目录 一、前言 二、http调用概述 2.1 什么是http调用 2.1.1 http调用步骤 2.2 HTTP调用特点 2.3 HTTP调用应用场景 三、微服务场景下http调用概述 3.1 微服务开发中http调用场景 3.2 微服务组件中http的应用 四、常用的http调用组件 4.1 java中常用的http组件介绍 4…