jemalloc 5.3.0的tsd模块的源码分析

一、背景

在主流的内存库里,jemalloc作为android 5.0-android 10.0的默认分配器肯定占用了非常重要的一席之地。jemalloc的低版本和高版本之间的差异特别大,低版本的诸多网上整理的总结,无论是在概念上和还是在结构体命名上在新版本中很多都找不到,而高版本尤其5.x.x版本就几乎没有现成的网上整理文档。这篇博客作为jemalloc 5.3.0专栏里的第一篇,后续会不断更新jemalloc 5.3.0版本的源码分析及实验对比,揭开jemalloc 5.3.0里的诸多实现上的细节。

这篇博客,我们会先介绍jemalloc 5.3.0里的tsd模块,为什么第一篇jemalloc 5.3.0的博客要介绍这个tsd模块呢?因为在jemalloc 5.3.0里,tsd模块算是一个基础组件模块,不先分析tsd模块,绕开它,会让源码分析困难重重。另外,tsd模块在实现上有不少编程上的技巧,不少技巧对于我们C++尤其C的开发人员也是非常值得借鉴的,尤其tsd模块里的若干宏定义和展开的实现,极简的优化了代码量,这种C的宏展开方式来达到的最终代码执行的效果,虽然从代码阅读上可能会有些晦涩,但是一旦掌握以后,它所带来的代码简化收益会相当可观,实现上可能会比替代用C++的大量的抽象和继承而定义的大量的冗余的类而言,从长期阅读观感上会更为极简。

我们会在第二章里先介绍jemalloc 5.3.0里的tsd模块的实现细节,其底层用到的glibc的tls机制细节可以参考之前的博客 线程局部存储tls的原理和使用-CSDN博客,然后在第三章里,我们对第二章里介绍的一些细节进行提炼和抽象,抽象出可以进行复用的编程上的技巧,作为我们一线开发人员长期编码上的一些参考。

二、jemalloc 5.3.0里的tsd模块的实现细节

我们先在 2.1 里介绍一下jemalloc 5.3.0的代码下载编译,并介绍个人在为了方便调试和调试分析的编译方式和进行jemalloc库的调试。在 2.2 里我们介绍tsd模块的用途和实现原理。

2.1 jemalloc 5.3.0的代码下载、编译和调试

2.1.1 代码下载和编译

jemalloc的github网址:

https://github.com/jemalloc/jemalloc

下载命令:

git clone https://github.com/jemalloc/jemalloc.git

cd jemalloc以后,执行如下命令切换到 5.3.0 版本:

git checkout 5.3.0

2.1.2 为了方便调试分析修改了一下源码和编译参数

关于tsd模块有一个宏定义的名字过长导致变量的名字过长,造成在通过vs2019进行ssh的gdb调试时,显示不出完整的变量名,造成代码分析的障碍,所以,临时为了调试方便,把该宏改短:

在tsd.h里,原始的宏定义如下:

临时改成(下面的名字可按照个人情况随意指定):

改完以后,先要执行自动生成编译用的文件的指令:

./autogen.sh

如果遇到如下错误:

则需要进行autoconf的安装:

apt-get update
apt-get install autoconf

安装完autoconf以后,重新执行./autogen.sh

在make之前,配置一下参数,使用O0,覆盖掉原来的O3:

./configure CFLAGS="-O0"

然后再执行make

如果遇到如下warning:

可以忽略,或者修改一下configure.ac文件里的ARFLAGS,下图是原始的内容:

修改成:

重新执行一遍上面的./autogen.sh ./configure xxx make clean;make -jx的流程以后,就不会遇到“ar: `u' 修饰符被忽略,因为 `D' 为默认(参见 `U')”的错误了。意思就是有了‘D’作为默认,ARFLAGS的‘u’就会被忽略,那就是不需要这个‘u’,去掉即可。

2.1.3 调试jemalloc库

经过上面的编译之后,生成物默认在jemalloc文件夹下的lib目录下,把它们拷贝到/usr/lib下:

然后我们参考 linux上对于so库的调试——包含通过vs2019远程ssh调试so库_vs2019 gdb调试-CSDN博客 这篇博客的方法进行远程ssh进行gdb调试jemalloc的库(博客里举的例子就是调试jemalloc库的例子)。

2.2 tsd模块的用途和实现原理

2.2.1 tsd模块借助的是glibc提供的tls机制

jemalloc 5.3.0里的tsd的意思是指Thread-Specific-Data,用的是之前的博客 线程局部存储tls的原理和使用-CSDN博客 提到的glibc的tls机制来实现的。jemalloc有关tsd的注释:

2.2.2 tsd模块实际用到的就4个文件

tsd模块的源文件就一个是src/tsd.c,头文件有多个:

但是对于x86_64 linux平台,根据tsd.h里下面这段根据编译选项来决定用那个头文件,而不用另外的几个头文件:

上图中,根据增加#err来确定,x86_64 linux平台,用的就是tsd_tls.h头文件。所以tsd模块里我们需要关注的头文件就只有下面这三个:

tsd.h

tsd_tls.h

tsd_types.h

所以,tsd模块在x86_64 linux平台实际用到的就4个文件:

tsd.c tsd.h tsd_tls.h tsd_types.h

2.2.3 tsd模块实现的核心是tsd.h文件,struct tsd_s按照TSD_DATA_SLOW、TSD_DATA_FAST、TSD_DATA_SLOWER定义不同的场景下用到的数据

在tsd.h的一开头的注释里有如下内容:

注释里清晰地表达了为了提高cache命中率,把不同场景下可能会用到的数据各自放到临近的区域,虽然TSD_DATA_SLOW、TSD_DATA_FAST、TSD_DATA_SLOWER用到的数据都定义在strcut tsd_s这个结构体里:

但是它们不同的path(fast-path或slow-path)用到的数据从定义的位置上都是连续的。

在tsd模块里的若干关键函数,如tsd_fetch_slow、tsd_state_set、tsd_add_nominal、tsd_remove_nominal等第一个入参tsd_t *tsd其实就是上图中的struct tsd_s这个结构体的指针:

上图中还有一个相关的定义是:

tsdn_s其实也是一样的struct tsd_s结构体的指针:

定义一个tsdn_t是为了和tsd_t来区分,tsdn_t是可以为NULL的,而tsd_t指针是由tls机制直接获取到的struct tsd_s数据的指针,如下图在tsd_tls.h里有定义:

而刚才说的tsd_fetch_slow、tsd_state_set、tsd_add_nominal、tsd_remove_nominal这些函数的第一个入参tsd_t *tsd实际上都是通过类似如下截图的函数tsd_get来获取到的:

所以,很显然它的地址是不可能是NULL的。相关的注释如下:

对于入参是tsdn_t *tsdn的函数如iallocztm等而言,传入TSDN_NULL和非NULL做区分可以用来表示特殊的含义,如下图:

2.2.4 详细分析一下TSD_DATA_SLOW、TSD_DATA_FAST、TSD_DATA_SLOWER这几个宏

在tsd模块的实现里,TSD_DATA_SLOW、TSD_DATA_FAST、TSD_DATA_SLOWER这三个宏可以说是关键。我们以TSD_DATA_SLOW宏为例,搜索后可以发现,它被反复的使用:

而且每次使用,它都有实际不同的含义:

我们分别来展开一下:

tsd.h里的第一处TSD_DATA_SLOW是在定义struct tsd_s这个结构体时:

TSD_DATA_SLOW展开是受#define O(n, t, nt)的影响的:

所以在struct tsd_s {的定义里,O(n, t, nt) 被定义成t TSD_MANGLE(n)

而TSD_MANGLE(n)被定义成(为了方便调试缩短了变量名,见 2.1.2 里的说明):

所以在struct tsd_s {的定义里就是声明了一下结构体里的成员,但是名字要加一个头。

再看第二处TSD_DATA_SLOW的使用,如下图,定义O是一个p_get_unsafe结尾的一个函数:

所以上面的TSD_DATA_SLOW展开就是定义了获取tsd_s结构体里的DATA_SLOW部分里的成员变量一个个的获取函数

再看第三处,和第二处差不多,是定义了获取tsd_s结构体里的DATA_SLOW部分里的成员变量一个个的带tsd的state的assert检查的函数(关于tsd的state的简要说明见 2.2.5 一节):

再看第四处,也和第二、第三处差不多,是定义了获取tsd_s结构体里的DATA_SLOW部分里的成员变量一个个的带入参检查的获取函数,因为入参是tsdn_t*,是可能是NULL的(在 2.2.3 里有说明):

第五处及以后就不一一展开了。

2.2.5 tsd的state的minimal和nominal

在这一章的最后,我们涉及一下tsd模块里的函数经常会碰到tsd的state有关的minimal和nominal的区别。

tsd里除了变量定义一块以外,还有一个tsd的state的状态维护逻辑。这篇博客先不涉及过深的状态差异上的细节,先讲提及最常见的tsd的state状态,即nominal状态。

tsd的state的定义在tsd.h里:

上图中红色框出的是最常见的nominal状态,已经初始化完了且是快速路径的是这个状态。

上面的三个状态0,1,2,到2为止,都是属于泛nominal状态,表示的是线程不处于创建和销毁时期而导致tsd“不完整”的状态。

nominal在jemalloc里的大致意思就是某种理想或者预期的分配方式,那对于内存分配,理想或者预期就是能分配效率比较快也就是快速路径的状态。

三、从tsd模块实现中可以借鉴的一些编码技巧

3.1 把不同场景高概率一起用到的数据放在一起连续的进行定义,从而增加cache的命中率

在上面 2.2.3 里讲到的tsd模块按照TSD_DATA_SLOW、TSD_DATA_FAST、TSD_DATA_SLOWER定义不同的数据块内容,虽然最终在一个结构体里,但是它们的定义是连续的,从而让数据所在的内存上的区域更容易临近,从而提到cache的命中率

3.2 通过define和undef宏定义里所依赖的宏,实现批量成员变量定义和批量函数定义

在上面的 2.2.4 一节里讲到,通过重新define和undef O(n, t, nt)来配合TSD_DATA_SLOW、TSD_DATA_FAST、TSD_DATA_SLOWER来实现批量的函数和成员变量的定义,这种宏操作可以用于简化一些重复冗余代码,方便统一化维护和修改

3.3 结构体指针的定义通过typedef不同的类型做代码直观上的轻度“解耦”,可用于体现一些入参范围的差异,如nullable和non-nullable

在 2.2.3 里我们讲到了 tsdn_t的定义是增加了NULL可能的tsd_t,我们可以借鉴这样的定义方式,来方便一些模块函数在参数传入需要做区分时的编程上的一定的解耦,方便后期的维护,也从代码里直观察觉到差异,避免参数各个场景下的误用和逻辑上的误判

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

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

相关文章

【Elasticsearch】Elasticsearch的查询

Elasticsearch的查询 DSL查询基础语句叶子查询全文检索查询matchmulti_match 精确查询termrange 复合查询算分函数查询bool查询 排序分页基础分页深度分页 高亮高亮原理实现高亮 RestClient查询基础查询叶子查询复合查询排序和分页高亮 数据聚合DSL实现聚合Bucket聚合带条件聚合…

DeepSeek R1有什么不同

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

macbook安装go语言

通过brew来安装go语言 使用brew命令时,一般都会通过brew search看看有哪些版本 brew search go执行后,返回了一堆内容,最下方展示 If you meant "go" specifically: It was migrated from homebrew/cask to homebrew/core. Cas…

文本左右对齐

hello 大家好!今天开写一个新章节,每一天一道算法题。让我们一起来学习算法思维吧! function fullJustify(words, maxWidth) {// 用于存储最终排版好的每一行文本const result [];// 用于遍历单词数组的索引,初始化为 0let i 0;…

Oracle 创建用户和表空间

Oracle 创建用户和表空间 使用sys 账户登录 建立临时表空间 --建立临时表空间 CREATE TEMPORARY TABLESPACE TEMP_POS --创建名为TEMP_POS的临时表空间 TEMPFILE /oracle/oradata/POS/TEMP_POS.DBF -- 临时文件 SIZE 50M -- 其初始大小为50M AUTOEXTEND ON -- 支持…

树状数组讲解

文章目录 1395.统计作战单位数 树状数组b站博主 灵神博主 tree数组:Tree[i] 存储的是原本的数组中num[i - (i&-i)1]到nums[i]的和 更新的时候,num[i[更新,逐一修改num[i(i & -i)] 307.区间和检索-数组可修改 题目实战 总的代码&#…

PostGIS笔记:PostgreSQL中表、键和索引的基础操作

创建、查看与删除表 在数据库中创建一个表,使用如下代码: create table streets (id serial not null primary key, name varchar(50));这里的表名是streets,id是主键所以非空,采用serial数据类型,这个数据类型会自动…

【JavaEE进阶】图书管理系统 - 壹

目录 🌲序言 🌴前端代码的引入 🎋约定前后端交互接口 🚩接口定义 🍃后端服务器代码实现 🚩登录接口 🚩图书列表接口 🎄前端代码实现 🚩登录页面 🚩…

大数据学习之SCALA分布式语言三

7.集合类 111.可变set一 112.可变set二 113.不可变MAP集合一 114.不可变MAP集合二 115.不可变MAP集合三 116.可变map一 package com . itbaizhan . chapter07 //TODO 2. 使用 mutable.Map 前导入如下包 import scala . collection . mutable // 可变 Map 集合 object Ma…

C++:多继承习题3

题目内容: 声明一个时间类Time,时间类中有3个私有数据成员(Hour,Minute,Second)和两个公有成员函数(SetTime和PrintTime)。要求: (1) SetTime根据传递的3个参数为对象设置时间; &a…

14-6-2C++STL的list

(一&#xff09;list对象的带参数构造 1.list&#xff08;elem);//构造函数将n个elem拷贝给本身 #include <iostream> #include <list> using namespace std; int main() { list<int> lst(3,7); list<int>::iterator it; for(itlst.begi…

Elasticsearch——Elasticsearch性能优化实战

摘要 本文主要介绍了 Elasticsearch 性能优化的实战方法&#xff0c;从硬件配置优化、索引优化设置、查询方面优化、数据结构优化以及集群架构设计等五个方面进行了详细阐述&#xff0c;旨在帮助读者提升 Elasticsearch 的性能表现。 1. 硬件配置优化 升级硬件设备配置一直都…

Linux进程调度与等待:背后的机制与实现

个人主页&#xff1a;chian-ocean 文章专栏-Linux 前言&#xff1a; 当一个进程发起某种操作&#xff08;如I/O请求、信号、锁的获取等&#xff09;&#xff0c;但该操作需要的资源暂时不可用时&#xff0c;进程会被操作系统挂起&#xff0c;进入“等待队列”或“阻塞状态”。…

【教学类-89-02】20250128新年篇02——姓名藏头对联(星火讯飞+Python,五言对联,有横批)

背景需求&#xff1a; 过年了&#xff0c;我想用幼儿的名字写对联&#xff0c;但是我根本不会写&#xff0c;于是尝试让AI来写。 1.我班的孩子的名字都是2字和3字的 2.惊喜发现&#xff0c;AI它很快就能生成带名字的对联 但是观察发现&#xff0c;如果是二个名字的对联&#…

Node.js基础

浏览器知识 浏览器 个浏览器都内置了DOM、BOM等API函数&#xff0c;供浏览器中的Javascript调用。 每个浏览器都有对应的JavaScript解析引擎。 浏览器中的JavaScript环境 V8引擎负责解析和执行JavaScript代码 内置API是由运行环境提供的特殊接口&#xff0c;只能在所属的运…

【漫话机器学习系列】066.贪心算法(Greedy Algorithms)

贪心算法&#xff08;Greedy Algorithms&#xff09; 贪心算法是一种逐步构建解决方案的算法&#xff0c;每一步都选择当前状态下最优的局部选项&#xff08;即“贪心选择”&#xff09;&#xff0c;以期望最终获得全局最优解。贪心算法常用于解决最优化问题。 核心思想 贪心选…

WPF基础 | WPF 常用控件实战:Button、TextBox 等的基础应用

WPF基础 | WPF 常用控件实战&#xff1a;Button、TextBox 等的基础应用 一、前言二、Button 控件基础2.1 Button 的基本定义与显示2.2 按钮样式设置2.3 按钮大小与布局 三、Button 的交互功能3.1 点击事件处理3.2 鼠标悬停与离开效果3.3 按钮禁用与启用 四、TextBox 控件基础4.…

GD32的GD库开发

所有的Cortex-M处理器都有相同的SysTick定时器&#xff0c;因为CMSIS-Core头文件中定义了一个名为SysTick的结构体。 这个定时器可以用作延时函数&#xff0c;不管是STM32的芯片还是GD32&#xff0c;AT32的芯片&#xff0c;delay函数都可以这么写&#xff0c;只要它是cortex-M…

跨域问题及解决方案

跨域问题不仅影响开发效率&#xff0c;还可能导致项目进度延误。因此&#xff0c;理解和掌握跨域问题的原理及其解决方案对于前端开发者和后端开发者来说都至关重要。本文将详细介绍什么是跨域、跨域产生的原因&#xff0c;以及常见的后端跨域解决方案。 文章目录 一、什么是跨…

MoE的学习

1.MoE的介绍 混合专家模型&#xff08;Mixture of Experts&#xff0c;MoE&#xff09;是一种先进的神经网络架构&#xff0c;旨在通过整合多个模型或“专家”的预测来提升整体模型性能。MoE模型的核心思想是将输入数据分配给不同的专家子模型&#xff0c;然后将所有子模型的输…