小小的 likely 背后却大有玄机!

4deff9dfe110ad7ff32e9c94e95a7209.gif

作者 | 张彦飞allen

来源 | 开发内功修炼

今天我给大家分享一个内核中常用的提升性能的小技巧。理解了它对你一定大有好处。

在内核中很多地方都充斥着 likely、unlikely 这一对儿函数的使用。随便揪两处,比如在 TCP 连接建立的过程中的这两个函数。

//file: net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{if (likely(!do_fastopen)) ...
}
//file: net/ipv4/tcp_input.c
int tcp_rcv_established(struct sock *sk, ...)
{if (unlikely(sk->sk_rx_dst == NULL))......
}

在刚开始看源码的时候,我也没弄懂这一对儿函数的玄机。后来才搞懂它原来对性能提升是有帮助的。今天我就来和大家聊聊 likely、unlikely 是如何帮助性能提升的。

1. likely 和 unlikely

咱们先来挖挖这对儿函数的底层实现。

//file: include/linux/compiler.h
#define likely(x)   __builtin_expect(!!(x),1)
#define unlikely(x) __builtin_expect(!!(x),0)

可以看到其实它们就是对 __builtin_expect 的一个封装而已。__builtin_expect 这个指令是 gcc 引入的。该函数作用是允许程序员将最有可能执行的分支告诉编译器,来辅助系统进行分支预测。

(参见 https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)

它的用法为:__builtin_expect(EXP, N)。意思是:EXP == N的概率很大。那么上面 likely 和 unlikely 这两句的具体含义就是:

  • __builtin_expect(!!(x),1) x 为真的可能性更大

  • __builtin_expect(!!(x),0) x 为假的可能性更大

当正确地使用了__builtin_expect后,编译器在编译过程中会根据程序员的指令,将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降。

2. 实际动手验证

那我们实际动手写两个例子,来窥探一下编译器是如何进行优化的。为此我写了一段简单的测试代码,如下:

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

这段代码非常简单,对用户的输入做一个 if else 判断。在 if 中使用了 likely,也就是假设这个条件为真的概率更大。那么我们来看看它编译后的汇编码来看看。

3a883ce2b620e8c50174de9f3f0b1400.png

图中上面红框内是对 if 的汇编结果,可见它使用的是 jne 指令。它的作用是看它的上一句比较结果,如果不相等则跳转到 4004bc 处,相等的话则继续执行后面的指令。

在 jne 指令后面紧挨着的是 n = n + 2 对应的汇编代码, 也就是说它把 n = n + 2 这段代码逻辑放在了紧挨着自己身边。而把 n = n - 2 的执行逻辑放在了离当前指令较远的 4004bc 处。

我们再把 likey 换成 unlikey 看一下,发现结果是正好相反,这次把 n = n - 2 的执行逻辑放在前面了,如图。

5bcc31253bf3445f4c78bbb3eb3320fa.png

注意,编译时需要加 -O2 选项,使用 objdump -S 来查看汇编指令。为了方便大家使用,我把它写到 makefile 里了,和测试代码一起放到了咱们的 Github 上了。

大家想动手的可以玩玩,地址:

https://github.com/yanfeizhang/coder-kung-fu/tree/main/tests/cpu/test01

3. 性能提升原理

那这么做为什么就能够提升程序运行性能呢?原因有两个。

首先第一个原因就是 CPU 高速缓存。现代的 CPU 一般都有三级的缓存,用来解决内存访问过慢的问题。访问数据时优先从 L1 读取,读取不到再读 L2、还读不到就读 L3,最后用内存兜底。

bdd192faca0e43cdcfe3ae794158ece7.png

这里要注意的是,每一次缓存数据的单位都是以一个 CacheLine 64 字节为单位进行存储的。假如说要查询的数据在 L1 中不存在,那么 CPU 的做法是一次性从 L2 中把要访问的数据及其后面的 64 个字节全部缓存进来。

假如下一次再执行的时候要访问的指令在上一次已经在 L1 中存在了,那么就直接访问 L1,就不必再从 L2 来读取了。回到上面的例子,假如说我们执行完了 cmp 对比指令以后,判断确实是要进加法分支,那紧接着就会执行 jne 后面的 mov xor 等指令大概率就可以从 L1 中取到了。

5a3cfd67ba6783481e2dd67dd8c6488a.png

假如说 cmp 对比执行后,发现是要跳到 4004bc 处的指令执行。那大概率该位置处的指令还没有被加载到缓存中(实践中一个分支下可能会包含很多代码,而不是像我这个例子中简单的两三行),就避免不了从 L2 L3 甚至是速度更慢的 L3 去读取指令。

除了高速缓存这个原因以外,还有一个更底层的原理。那就是 CPU 的流水线技术。CPU 在执行程序指令的时候,并不是先执行一个,执行完了再运行下一个这样的。而是把每个指令都分成了多个阶段,并让不同指令的各步操作重叠,从而实现几条指令并行处理。

还拿上面编译出来的汇编码来举例,程序中 cmp、jne、mov 几个指令是挨着的,那么 CPU 在执行的时候实际是大概这样的。

3c41807039b3a486f0597d9849983f5c.png

当 jne 指令正在执行的时候,后面的两个 mov 指令都已经分别进入到译码和取址阶段了都。假如说分支预测失败,那么这工作就白干了。

4. 小结

总结一下,今天分享的 likely 和 unlikely 其实是属于是辅助 CPU 分支预测的性能优化方法。这就是 likely 和 unlikely 背后的这点小秘密。它是为了让 CPU 的高速缓存命中率更高,同时也为了让 CPU 的流水线更好地工作。

Linux 作为一个基础程序,在性能上真的是考虑到了极致。内核的作者们内功都是非常的深厚,都深谙计算机的底层工作原理。为了极致的性能追求精心打磨每一个细节,非常值得我们学习和借鉴。

不过这里要说的一点是,likely 和 unlikely 的概率判断务必准确。如果写反了,反而会起到反作用。

e52232565c2410269e858fcd209f3a3c.gif

往期推荐

如果让你来设计网络

写时复制就这么几行代码,还是不会?

JavaScript 数组你都掰扯不明白,还敢说精通 JavaScript ?

明明还有大量内存,为啥报错“无法分配内存”?

0ec3caa96336c4a688023004cd4d7320.gif

点分享

fafc1349c2b9c03fd7508605cf93a4d8.gif

点收藏

5d077953a2a2c88118f5fd5140650294.gif

点点赞

7f274748214b44cf6a13b7e249f4e9fd.gif

点在看

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

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

相关文章

阿里云马涛:因云进化的基础软件

简介&#xff1a; 基础软件的云原生化。 编者按&#xff1a;2021 年10 月20 日&#xff0c;在2021 云栖大会云计算产业升级峰会上&#xff0c;阿里云“因云而生”云原生心智大图正式发布&#xff0c;包含弹性计算、云网络、基础产品、基础设施、操作系统、云安全、开放平台等7个…

阿里云ECI如何6秒扩容3000容器实例?

简介&#xff1a; 2021年云栖大会现场&#xff0c;阿里云工程师演示了在6秒时间内成功启动3000个ECI&#xff0c;并全部进入到Running状态。本文将为你揭开阿里云ECI是如何做到极速扩容的。 引言 根据最新CNCF报告&#xff0c;有超过90%的用户在生产环境使用容器&#xff0c;…

巧用友盟+U-APM 实现移动端性能优化—启动速度

简介&#xff1a; 移动端性能对用户体验、留存有着至关重要的影响&#xff0c;作为开发者是不是被这样吐槽过&#xff0c;“这个 APP 怎么这么大&#xff1f;”、“怎么一直在 APP 封面图转悠&#xff0c;点不进去”、“进入详情效果有些卡”、“用 4G 使用你们的 APP&#xff…

第25版 OpenStack Yoga 已发布

OpenStack社区今日正式发布第25版-Yoga&#xff0c;该版本通过支持先进的硬件技术如SmartNIC DPUs&#xff0c;优化与云原生软件如Kubernetes、Prometheus等的集成以及减少技术债等方式来保持OpenStack内核的稳定性与可靠性。 OpenStack作为开源基础设施即服务&#xff08;Iaa…

项目实战总结以及接入U-APM

简介&#xff1a; 导致 App 性能低下的原因有很多&#xff0c;除去设备硬件和软件的外部因素&#xff0c;其中大部分是开发者错误地使用线、系统函数、编程范式、数据结构等导致的。即便是较有经验的程序员&#xff0c;也很难在开发时就能避免所有导致性能低下的“坑”&#xf…

oracle redo 200mb,Oracle的redo log在各场景下的恢复

Oracle的redo log非常重要&#xff0c;redo log损坏将导致数据库开法开启或数据丢失&#xff0c;针对redo log在各种场景下如何打开或恢复数据库&#xff0c;特别模拟测试说明&#xff1a;各场景包括如下(共6个场景):场景一.非归档下inactive状态的redo 恢复场景二.非归档下act…

站在原地就是退步——除了死磕通道,云通讯服务商还该做些什么?

受访嘉宾&#xff1a;吴佳钊&#xff0c;杭州云片网络科技有限公司联合创始人、CTO 当前&#xff0c;全球通信云已经步入2.0时代&#xff0c;最大的变化在于通信形式的变革&#xff1a;传统短信语音的通信形式将逐步向包括即时通讯IM实时音视频RTC的互联网通信转变。尤其在5G时…

Cube 技术解读 | 详解「支付宝」全新的卡片技术栈

简介&#xff1a; 魔方卡片&#xff08;Cube&#xff09;&#xff0c;让 App 首页实现敏捷更新。 CodeHub#7 正式落幕&#xff0c;来自蚂蚁集团的技术专家「京君」与掘金社区的开发者们分享了「支付宝」全新的卡片技术栈——魔方卡片&#xff08;Cube&#xff09;。 京君围绕 C…

庖丁解InnoDB之REDO LOG

简介&#xff1a; 数据库故障恢复机制的前世今生一文中提到&#xff0c;今生磁盘数据库为了在保证数据库的原子性(A, Atomic) 和持久性(D, Durability)的同时&#xff0c;还能以灵活的刷盘策略来充分利用磁盘顺序写的性能&#xff0c;会记录REDO和UNDO日志&#xff0c;即ARIES方…

Web 自动化神器,批量下载美图,可直接导入使用

‍‍作者 | 小碗汤来源 | 进击云原生今天为大家分享一款前端自动化操作神器: Automa。Automa介绍它是一款 Chrome 插件&#xff0c;即使你不会写代码&#xff0c;也能按照自己的需求&#xff0c;完成一系列自动化操作。利用它&#xff0c;你可以将一些重复性的任务实现自动化、…

RocketMQ 5.0 POP 消费模式探秘

简介&#xff1a; POP Consumer—使客户端无状态&#xff0c;更轻量&#xff01; 作者&#xff1a;凯易&耘田 前言&#xff1a;随着 RocketMQ 5.0 preview 的发布&#xff0c;5.0 的重大特性逐步与大家见面。POP Consumer 作为 5.0 的一大特性&#xff0c;POP 消费模式展现…

【ESSD技术解读-01】 云原生时代,阿里云块存储 ESSD 快照服务如何被企业级数据保护所集成?

简介&#xff1a; 本文描述了阿里云块存储快照服务基于高性能 ESSD 云盘提升快照服务性能&#xff0c;提供轻量、实时的用户体验及揭秘背后的技术原理。依据行业发展及云上数据保护场景&#xff0c;为企业用户及备份厂商提供基于快照高级特性的数据保护的技术方案&#xff0c;满…

一把王者的时间,我就学会了Nginx

作者 | 步尔斯特来源 | CSDN博客Nginx 简介Nginx("engine x")是一个高性能的 HTTP 和反向代理服务器,特点是占有内存少&#xff0c;并发能力强&#xff0c;事实上 nginx 的并发能力确实在同类型的网页服务器中表现较好&#xff0c;中国大陆使用 nginx 网站用户有&…

【ESSD技术解读-02】企业级利器,阿里云 NVMe 盘和共享存储

简介&#xff1a; 当前 NVMe 云盘结合了业界最先进的软硬件技术&#xff0c;在云存储市场&#xff0c;首创性同时实现了 NVMe 协议 共享访问 IO Fencing 技术。它在 ESSD 之上获得了高可靠、高可用、高性能&#xff0c;同时基于 NVMe 协议实现了丰富的企业特性&#xff0c;如…

php数组json函数,php数组转json的函数是什么

php数组转json的函数是json_encode()。json_encode()函数可以对变量进行JSON编码&#xff0c;将其转换为json字符串数据&#xff0c;语法格式“json_encode (value)”。本教程操作环境&#xff1a;windows7系统、PHP7.1版&#xff0c;DELL G3电脑php数组如何转为json&#xff1…

使用友盟+的APM服务实现对移动端APP的性能监控

简介&#xff1a; 对于信息系统服务&#xff0c;一般我们的重点监控对象都是核心的后端服务&#xff0c;通常会采用一些主流的APM(Application Performance Management)框架进行监控、告警、分析。那么对于移动端的APP、小程序的运行时状态如何进行实时监控与分析呢&#xff1f…

首届“中国物联网数据基础设施最佳案例评选”结果出炉

供稿 | 映云科技 出品 | CSDN云计算 随着物联网技术的成熟与普及&#xff0c;如今的世界早已进入万物互联的时代&#xff0c;全球年活跃连接的物联网设备已达数百亿规模 &#xff08;IoT Analytics, 2021&#xff09;。海量物联设备产生的数据&#xff0c;需要通过统一汇聚和…

Serverless 工程实践 | 快速搭建 Kubeless 平台

简介&#xff1a; Kubeless 是基于 Kubernetes 的原生无服务器框架。其允许用户部署少量的代码&#xff08;函数&#xff09;&#xff0c;而无须担心底层架构。 快速搭建 Kubeless 平台 Kubeless 简介 Kubeless 是基于 Kubernetes 的原生无服务器框架。其允许用户部署少量的…

并发编程实践之公平有界阻塞队列实现

简介&#xff1a; JUC 工具包是 JAVA 并发编程的利器。本文讲述在没有 JUC 工具包帮助下&#xff0c;借助原生的 JAVA 同步原语, 如何实现一个公平有界的阻塞队列。希望你也能在文后体会到并发编程的复杂之处&#xff0c;以及 JUC 工具包的强。 作者 | 李新然 来源 | 阿里技术公…

iOS App 启动优化

简介&#xff1a; 作为程序猿来说&#xff0c;“性能优化”是我们都很熟悉的词&#xff0c;也是我们需要不断努⼒以及持续进⾏的事情&#xff1b;其实优化是⼀个很⼤的课题&#xff0c;因为细分来说的话有⼤⼤⼩⼩⼗⼏种优化⽅向 &#xff0c;但是切忌在实际开发过程中不能盲⽬…