手机网站dedecms,wordpress 数据库管理,wordpress中文主题站,网站为什么做优化ppt一、背景
在主流的内存库里#xff0c;jemalloc作为android 5.0-android 10.0的默认分配器肯定占用了非常重要的一席之地。jemalloc的低版本和高版本之间的差异特别大#xff0c;低版本的诸多网上整理的总结#xff0c;无论是在概念上和还是在结构体命名上在新版本中很多都…一、背景
在主流的内存库里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这个结构体里 但是它们不同的pathfast-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我们可以借鉴这样的定义方式来方便一些模块函数在参数传入需要做区分时的编程上的一定的解耦方便后期的维护也从代码里直观察觉到差异避免参数各个场景下的误用和逻辑上的误判