jemalloc 5.3.0源码总结

请添加图片描述
注意:jemalloc 的最新版本里没有所谓的 huge class,bin 中slab外面也不再套一个run的概念了,看其它人分享的文章时需要注意。

简述

用户侧通过 tcache 来访问,tcache 是一个线程的申请又释放的对象的缓存,它绑定了一个固定的arena(分配大小超过8m的除外,8m以上会选择huge arena 来分配),有一个gc机制负责将不用的object 归还给 arena。
arena 分配对象时以8k为界限,以上均为大对象,以下均为小对象。小对象是按 slab 去管理的,即每次从 huge page 或 page 分配一个大的 extent 作为 slab,划分后将整个slab挂在bin的对应大小的组上,然后标记一个返回给用户;大对象则直接分配一个extent 放在arena 的large list上。
arena 是一个逻辑的概念,它真实的数据分配是在shard上进行的,各arena 有不同的shard,内存之间不相交,shard 有 hpa(huge page)和 pac(page)两种分配器,优先使用hpa来分配,但hpa只能承担最多一个huge page 大小的内存,再大就只能用pac去分配。
hpa 在分配前会先用base分配器申请128 个大页,每次分配时从某个大页上切一块下来,pac每次缓存的内存不够时会通过base分配器来分配
base 分配器一次分配一个block,并存在base 的 list 上。
pac,hpa,base都有一个类型的缓存结构来维护退回来的内存(ppset_t,ecache_t,edata_avail_t)。它们都是用bitmap 维护一个bin列表,bin中的每个元素是符合这一组内存大小的pairing heap最小堆。

size 与 bin 中 index 的映射

每个bin index对应的大小可以直接查表。
从size 到bin index 的关系在4k以下是可以查表的,4k以上需要计算
group = size / total_group / min_size
group 内的 delta = 1…(size / (total_group + 1))个0 & (size -1)去掉group的位
具体可以参考(sz_size2index_compute)
请添加图片描述
每个size class 的具体情况可以跑这个代码看下

scs = [None] * 1024class SC:def __init__(self, lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta) -> None:self.index = indexself.lg_base = lg_baseself.lg_delta = lg_deltaself.ndelta = ndeltaself.unit_size =  (1 << self.lg_base) + (ndelta << self.lg_delta)# should from os, default 4096self.page_size = (1 << lg_page)# small or largeself.is_small =  (self.unit_size < (1 << (lg_page + lg_ngroup)))self.pages = self.get_slab_size() if self.is_small else 0# small or largeself.lg_delta_lookup = lg_delta if (self.unit_size <= (1 << lg_max_lookup))  else 0 def get_slab_size(self):# at most lg_group * 2 - 1 timesuse_size = self.page_sizewhile (use_size % self.unit_size) != 0:use_size += self.page_sizereturn use_sizedef size_classes(lg_ptr_size, lg_quantum, lg_tiny_min, lg_max_lookup, lg_page, lg_ngroup):lg_base = lg_tiny_minlg_delta = lg_basengroup = 1 << lg_ngroupptr_bits = (1 << lg_ptr_size) * 8index = 0ndelta = 0nlbins = 0 # max lookup table sizenbins = 0ntiny = 0lg_tiny_maxclass = 0# tiny size is 2^n enlargingwhile lg_base < lg_quantum:sc = SC(index, lg_base, lg_delta, ndelta, lg_page, lg_ngroup, lg_max_lookup)scs[index] = scif sc.lg_delta_lookup:nlbins = index + 1if sc.is_small:nbins += 1ntiny += 1lg_tiny_maxclass = lg_baseindex += 1lg_delta = lg_baselg_base += 1if ntiny != 0:# first non tiny sc# use base/delta = quatum-1 rather than base = quatum and delta = 0lg_base -= 1first_non_tiny_sc = SC(lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, 1)scs[index] = first_non_tiny_sclg_base += 1index += 1lg_delta += 1if sc.is_small:nbins += 1while ndelta < ngroup:sc = SC(lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta)scs[index] = scindex += 1ndelta += 1if sc.is_small:nbins += 1# other groupslg_base = lg_base + lg_ngroupwhile lg_base < ptr_bits - 1:ndelta = 1ndelta_limit = ngroup - 1 if lg_base == (ptr_bits - 2) else ngroupwhile ndelta <= ndelta_limit:sc = SC(lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta)scs[index] = scif sc.lg_delta_lookup:nlbins = index + 1lookup_maxclass = (1 << lg_base) + (ndelta << lg_delta)if sc.is_small:nbins += 1small_maxclass = (1 << lg_base) + (ndelta << lg_delta)lg_large_minclass = lg_base + 1 if lg_ngroup > 0 else lg_base + 2large_maxclass = (1 << lg_base) + (ndelta << lg_delta)print("large_maxclass:", lg_base, ndelta, lg_delta, large_maxclass)index += 1ndelta += 1lg_base += 1lg_delta += 1print(f"""ntiny: {ntiny}
nlbins: {nlbins}
nbins: {nbins}
n: {index}
lg_tiny_maxclass: {lg_tiny_maxclass}
lookup_maxclass: {lookup_maxclass}
small_maxclass: {small_maxclass}
lg_large_minclass: {lg_large_minclass}
large_minclass: {1 << lg_large_minclass}
large_maxclass: {large_maxclass}""")indexs = []bases = []deltas = []unit_sizes = []is_small = []pages = []print("""index\tbase\tdelta\tunit_size\tis_small\tpages""")for i in range(1,1024):if scs[i] is None:breaksc = scs[i]indexs.append(i)bases.append(sc.lg_base)deltas.append(sc.ndelta << sc.lg_delta)unit_sizes.append(sc.unit_size)is_small.append("yes" if sc.is_small else "no")pages.append(sc.pages)print(f"""{i}\t{1 << sc.lg_base}\t{sc.ndelta << sc.lg_delta}\t{sc.unit_size}\t{"yes" if sc.is_small else "no"}\t{sc.pages}""")LG_SIZEOF_PTR = 3
LG_QUANTUM = 4
SC_LG_TINY_MIN = 3
SC_LG_MAX_LOOKUP = 12 
LG_PAGE = 12
SC_LG_NGROUP = 2
size_classes(LG_SIZEOF_PTR, LG_QUANTUM, SC_LG_TINY_MIN, SC_LG_MAX_LOOKUP, LG_PAGE, SC_LG_NGROUP)SC_NTINY = (LG_QUANTUM - SC_LG_TINY_MIN)
SC_NGROUP = (1 << SC_LG_NGROUP)
SC_NPSEUDO = SC_NGROUP
SC_LG_FIRST_REGULAR_BASE = (LG_QUANTUM + SC_LG_NGROUP)
SC_NBINS = SC_NTINY + SC_NPSEUDO + SC_NGROUP * (LG_PAGE + SC_LG_NGROUP - SC_LG_FIRST_REGULAR_BASE)
print("SC_NBINS is :", SC_NBINS)

cache 的 gc

tcache 大概是这样的:
bound --------full---------waterline----empty
|------------------|----------------|-------------|
其中当数量超过full时,触发一次flush,将一半的对象还回去,剩余的数量称为waterline。(除此之外好像在flush后,又分配对象,然后又释放对象,重回到waterline 以后,比waterline 多的对象作为detach 的对象也可以被flush 走,但没看太懂,不太确定)

大小对象的分配与释放

无论大小对象都是先从tcache分配,检查bitmap 看对应大小的对象有没有剩余,如果有直接拿,如果没有才去arena 分配。

小对象(<8k)

小对象分配是在arena 的 bin 上,每个大小的bin都对应着一系列的slab。分配时先从cur_slab分配,如果cur_slab没有了,就取一个nonfull list的slab放在cur_slab上 分配,如果nonfull list 也没有了,就去pac或hpa重新分配slab,挂在cur_slab上分配。当cur_slab分配没了,就会挂在full slab上,在释放时发现它在full slab 上,则放回到nonfull slab 上。分配的指针与 arena 还有 size 的映射存在arena_emap_global 上。
请添加图片描述

大对象(>8k)

直接走 pac 或 hpa 分配和释放。分配后挂在arena->large_list 上。有arena_emap_global来维护指针到large_list上edata 元素的映射。释放时也是直接从 large list 上拿走。

internal 分配器

arena 有两种:普通+huge。大于 8m(oversize_threshold) 的对象是在 huge arena 上分配的,小于 8m 的在自己绑定的普通 arena 上分配(除非是用户指定了arena,这时才不会自动转到 huge arena 上分配)。
无论哪种分配器,在分配时都会先尝试用 hpa(hpa 最多 hold 一个大页以下的内存,一般linux 配置是2m),如果超出 hpa 的能力才去普通 pac 分配。

hpa 分配器

hpa 在第一次分配时会申请 128 个 hugepage放在empty list 中。分配时先从 psset 缓存找对应大小的 bitmap,看有没有空闲,如果有就从这个大小的pairing heap上拿最小空闲的一个huge page 下来(这个最小空闲指在这个huge page 上最大的一段连续内存的大小,在所有同组huge page 中是最小的
),如果没有空闲就找下一个更大大小的组,直到找到一个 huge page。根据 huge page的bitmap找到对应的位置,并更新这个 huge page 上最大的没分配范围。
分配出来的内存由edata来记录,并放在shard->emap 上,这是一个前缀树,可以在释放时根据前缀树找到是否有相邻的释放内存,并对它们做合并。
分配后的 huge page 会根据剩余的最大内存重新挂回到psset 上。
释放时内存先从emap 上摘下来,然后在如果它的回归能够让huge page 的最大空闲更新,则把huge page 插入到更合适的 bin 中。释放时如果发现一页完全空了,则可以添加回到psset->empty list中。这些做完后还要标记这个范围到psset->to_purge,会在合适的时间触发purge(实际是提示操作系统不再使用madvice(NONEED))
请添加图片描述

pac 分配器

pac 也有类似的缓存,但过程不全相同。pac 的页合并不像huge page 那样用bitmap,而是借助 shard_map (见上图) 。pac 在分配时会先找完全满足的bin去分配,如果配置的bin没有空闲,它会找更大一点的bin来split,但只会向上找最多opt_lg_extent_max_active_fit个bin。如果新分配出来的内存对extent 做了切割,那么切割剩的extent 会加入到retained 缓存中。
pac 有三个缓存,dirty / muzzy / retained。分配时先去 dirty cache 找匹配的或能切割的,再去 muzzy 找,如果都找不到,会去 retained 中找,retained 中找不到时才真正去 base 分配,并在切割后将剩余的放到retained 中。
之所以新分配的内存还是有切割可能,是因为pac 每次去 base 进货的单位内存是不断增加的,进货的单位内存有一个 pac->exp_grow 来维护(它有一个增长到的最大 limit值,但应该不起作用,因为limit 值设置为了最大object class size,而这个值对于64位的指针来说就是2^63。)
muzzy 与 dirty 内存的区别: muzzy 的 lazy purge 的时间会更久,dirty 内存在 purge 时,会检查一下可否转为 muzzy,如果能转,则它 purge 的时间会更 delay。这样可以避免频繁向操作系统申请和释放。

base 分配器

base 真正分配 block 时也像 pac 一样有一个申请内存的单位不断增长的机制(由 base->pind_last记录上一次分配的大小级别,下次分配只能比这个级别相等或更大)
真实的分配是由sbrk(dss方式) 或 mmap 系统调用来分配的,默认是先用 mmap 分配(DSS_DEFAULT =“secondary”),用户可以调整。
dss 比 mmap 快,但没有 mmap 灵活。

bitmap

为了加速查找,bitmap 采用的是多层的形式,对当于一个bit-skip-list。

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

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

相关文章

C/C++ ——内存管理

前言 为什么要研究内存管理&#xff1f; (1)程序员写的程序可以分为动态和静态两种状态。静态&#xff1a;就是程序被存放在ROM中&#xff0c;也就是磁盘、固态硬盘、eMMC等存储介质&#xff1b;动态&#xff1a;程序被执行&#xff0c;此时程序在RAM内存中运行&#xff1b; (…

Cpp/Qtday030908cpp基础

目录 目录 自行封装一个栈的类&#xff0c;包含私有成员属性&#xff1a;栈的数组、记录栈顶的变量 成员函数完成&#xff1a;构造函数、析构函数、拷贝构造函数、入栈、出栈、清空栈、判空、判满、获取栈顶元素、求栈的大小 头文件&#xff1a;stack.h 源文件: stack.cp…

Linux相关指令(下)

cat指令 查看目标文件的内容 常用选项&#xff1a; -b 对非空输出行编号 -n 对输出的所有行编号 -s 不输出多行空行 一个重要思想&#xff1a;linux下一切皆文件&#xff0c;如显示器文件&#xff0c;键盘文件 cat默认从键盘中读取数据再打印 退出可以ctrlc 输入重定向<…

日常开发中,提升技术的13个建议

最近有位好友问我&#xff1a;日常开发中&#xff0c;都是在做业务需求&#xff0c;如何提升自己的技术呢&#xff1f;因此&#xff0c;本文小黑整理了提升技术的13个建议&#xff0c;小伙伴们&#xff0c;一起加油。 1. 打好基础,深入学习语言特性 比如&#xff0c;对于Java程…

7英寸触摸显示屏企业网络电话

SV-X77英寸触摸显示屏企业网络电话 SV-X7网络电话是一款带有7英寸触摸显示屏的高端式企业级电话&#xff0c;以先进设计及强大的功能大幅度提高企业工作效率。 功能亮点 √ 虚拟可编程按键 — 可动态显示4个分页&#xff0c;每页可设置显示29个DSS键的状态&#xff0c;最多支持…

记一次以太网连接失败修复

症状: 很久没用这个电脑了&#xff0c;开机以后&#xff0c;发现连不上校园网。 遂检查网线&#xff0c;发现网线连在自己笔记本是可以用的&#xff0c;说明网线没问题。 但是网线连在主机是红灯常亮黄灯闪烁&#xff0c;怀疑是网卡有问题&#xff08;后证明不是&#xff0c…

手机上pdf转word的方法,快来掌握一下吧

在日常工作和学习中&#xff0c;我们常常需要将PDF文件转为Word文档以方便编辑和修改。本文将介绍手机上PDF转Word的方法及需要注意的事项。 PDF转Word的方法 目前市面上有很多PDF转Word的应用&#xff0c;通常都是在电脑上操作的&#xff0c;但是很多时候我们不在电脑前如果需…

智慧能源方案:TSINGSEE青犀AI算法中台在能源行业的应用

一、方案背景 互联网、物联网、人工智能等新一代信息技术引领新一轮产业革命&#xff0c;加快能源革命步伐。尤其是随着人工智能技术的不断发展&#xff0c;AI智能检测与识别技术在能源行业的应用也越来越广泛。与此同时&#xff0c;国家出台多项政策&#xff0c;将智慧能源纳…

【ALM工具软件】上海道宁与Perforce为您带来用于整个生命周期的应用程序生命周期管理软件

Helix ALM是 用于整个生命周期的 应用程序生命周期管理的ALM软件 具有专用于 需求管理&#xff08;Helix RM&#xff09;、测试用例管理&#xff08;Helix TCM&#xff09; 问题管理&#xff08;Helix IM&#xff09;的功能模块 Helix ALM提供了 无与伦比的可追溯性 您将…

OceanBase 里的 schema 是什么?

李博洋 OceanBase 技术部研发工程师。 OceanBase 开源社区里经常会看到一些类似于 “ schema 是什么” 的疑问&#xff1a; 很多同学经常会误以为在 OceanBase 里&#xff0c;schema 只是 database 的同义词&#xff0c;这次分享就从 schema 是什么这个问题稍微展开聊一下。 首…

Java多线程4种拒绝策略

文章目录 一、简介二、AbortPolicy拒绝策略A. 概述B. 拒绝策略实现原理C. 应用场景D. 使用示例 三、CallerRunsPolicy拒绝策略A. 概述B. 拒绝策略实现原理C. 应用场景D. 使用示例 四、DiscardPolicy拒绝策略A. 概述B. 拒绝策略实现原理C. 应用场景D. 使用示例 五、DiscardOldes…

libQGLViewer的编译和使用

文章目录 libQGLViewer的编译和使用1 前言2 libQGLViewer开发环境搭建2.1 Qt Creator的下载安装2.2 libQGLViewer的下载编译2.3 安装Qt Designer 的QGLViewer控件插件&#xff08;可选&#xff09;2.3 关于Qt Designer 的QGLViewer控件插件的一些问题 3 在自己的项目中调用4 总…