jemalloc 5.3.0源码总结

news2024/11/23 18:39:04

请添加图片描述
注意: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] * 1024

class SC:
    
    def __init__(self, lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta) -> None:
        self.index = index
        self.lg_base = lg_base
        self.lg_delta = lg_delta
        self.ndelta = ndelta
        self.unit_size =  (1 << self.lg_base) + (ndelta << self.lg_delta)
        # should from os, default 4096
        self.page_size = (1 << lg_page)
        # small or large
        self.is_small =  (self.unit_size < (1 << (lg_page + lg_ngroup)))
        self.pages = self.get_slab_size() if self.is_small else 0
        # small or large
        self.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 times
        use_size = self.page_size
        while (use_size % self.unit_size) != 0:
            use_size += self.page_size
        return use_size
    
def size_classes(lg_ptr_size, lg_quantum, lg_tiny_min, lg_max_lookup, lg_page, lg_ngroup):
    lg_base = lg_tiny_min
    lg_delta = lg_base
    ngroup = 1 << lg_ngroup
    ptr_bits = (1 << lg_ptr_size) * 8
	
    index = 0
    ndelta = 0
    nlbins = 0 # max lookup table size
    nbins = 0
    ntiny = 0
    lg_tiny_maxclass = 0
    # tiny size is 2^n enlarging
    while lg_base < lg_quantum:
        sc = SC(index, lg_base, lg_delta, ndelta, lg_page, lg_ngroup, lg_max_lookup)
        scs[index] = sc
        if sc.lg_delta_lookup:
            nlbins = index + 1
        if sc.is_small:
            nbins += 1
        ntiny += 1
        lg_tiny_maxclass = lg_base
        index += 1
        lg_delta = lg_base
        lg_base += 1
    
    if ntiny != 0:
        # first non tiny sc
        # use base/delta = quatum-1 rather than base = quatum and delta = 0
        lg_base -= 1
        first_non_tiny_sc = SC(lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, 1)
        scs[index] = first_non_tiny_sc
        lg_base += 1
        index += 1
        lg_delta += 1
        if sc.is_small:
            nbins += 1

    while ndelta < ngroup:
        sc = SC(lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta)
        scs[index] = sc
        index += 1
        ndelta += 1
        if sc.is_small:
            nbins += 1
        
    # other groups
    lg_base = lg_base + lg_ngroup
    while lg_base < ptr_bits - 1:
        ndelta = 1
        ndelta_limit = ngroup - 1 if lg_base == (ptr_bits - 2) else ngroup
        while ndelta <= ndelta_limit:
            sc = SC(lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta)
            scs[index] = sc
            if sc.lg_delta_lookup:
                nlbins = index + 1
                lookup_maxclass = (1 << lg_base) + (ndelta << lg_delta)
            if sc.is_small:
                nbins += 1
                small_maxclass = (1 << lg_base) + (ndelta << lg_delta)
                lg_large_minclass = lg_base + 1 if lg_ngroup > 0 else lg_base + 2
            large_maxclass = (1 << lg_base) + (ndelta << lg_delta)
            print("large_maxclass:", lg_base, ndelta, lg_delta, large_maxclass)
            index += 1
            ndelta += 1
        lg_base += 1
        lg_delta += 1

    print(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:
            break
        sc = 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.coloradmin.cn/o/989728.html

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

相关文章

紫光电子档案管理系统存在SQL注入漏洞(漏洞复现)

文章目录 紫光电子档案管理系统存在SQL注入漏洞&#xff08;漏洞复现&#xff09;0x01 前言0x02 漏洞描述0x03 影响范围0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 紫光电子档案管理系统存在SQL注入漏洞&#xff08;漏洞复现&#xff09; 0x01 前言 本次测试仅…

数据结构与算法-----指针与结构体

目录 前言 指针 定义 示例1&#xff08;访问修改数据&#xff09;&#xff1a; 示例2&#xff08;野指针&#xff09;&#xff1a; 示例3&#xff08;动态空间分配问题&#xff09;&#xff1a; 示例4&#xff08;字符串赋值错误问题&#xff09;&#xff1a; 示例5&am…

2023年限售股解禁研究报告

第一章 概述 解禁是指限售流通股过了限售承诺期&#xff0c;可以在二级市场自由买卖的过程。根据流通性质&#xff0c;可将上市公司股份分为有限售条件的流通股&#xff08;“限售流通股”&#xff09;及无限售条件的流通股&#xff08;“流通股”&#xff09;。 限售流通股指…

案例实战-Spring boot Web

准备工作 需求&环境搭建 需求&#xff1a; 部门管理&#xff1a; 查询部门列表 删除部门 新增部门 修改部门 员工管理 查询员工列表&#xff08;分页、条件&#xff09; 删除员工 新增员工 修改员工 环境搭建 准备数据库表&#xff08;dept、emp&#xff09; -- 部门管理…

NLP(3)--GAN

目录 一、概述 二、算法过程 三、WGAN 1、GAN的不足 2、JS散度、KL散度、Wasserstein距离 3、WGAN设计 四、Mode Collapse and Mode Dropping 1、Mode Collapse 2、Mode Dropping 3、FID 四、Conditional GAN 一、概述 GAN&#xff08;Generative Adversial Networ…

【云原生进阶之PaaS中间件】第一章Redis-1.7发布订阅模式

1 Redis 发布订阅 1.1 概述 发布订阅模式&#xff08;Publish-Subscribe Pattern&#xff09;是一种消息传递模式&#xff0c;其基本原理是消息的发送者&#xff08;发布者&#xff09;不会直接发送消息给特定的接收者&#xff08;订阅者&#xff09;&#xff0c;而是将消息分…

基于SpringBoot的无忌在线考试系统(源码+讲解+调试运行)做毕设课设均可

技术栈 前后端分离 前端使用: Vue Element Plus 后端使用: SpringBoot Mysql8.0 Mybatis-Plus 功能 分为 管理员端 和 老师端 和 学生端 管理员端 登陆页 ​科目管理 查看所有科目 ,增加 ,修改 ,删除科目 , 模糊搜索课程 ​考试管理 查看所有考试 ,增加 ,修改 ,删除考试 题库…

Linux之Shell变量和引用

目录 深入认识变量 什么是变量 变量的名称 组成 规则 变量的类型 原则 shell的变量数据类型 变量定义 原则 格式 案例 自定义变量 定义 引用变量的值 查看变量 环境变量 定义 定义环境变量 案例 --- 三种方法定义 对比 shell环境变量存储的文件 位置变量…

Python - PyQt6、QDesigner、pyuic5-tool 安装使用

Python 开发可视化界面可以使用原生的 tkinter&#xff0c;但是原生框架使用起来颇为不方便&#xff0c;所以最流行的还是QT UI框架&#xff0c;QT是使用C语言开发&#xff0c;Python 想使用需要对其进行封装&#xff0c;所以就出现了PyQt框架&#xff0c;这个框架使用极其方便…

c语言练习44:深入理解strstr

深入理解strstr strstr作用展示&#xff1a; #include <stdio.h> #include <string.h> int main() {char str[] "This is a simple string";char* pch;pch strstr(str, "simple");/*strncpy(pch, "sample", 6);*/printf("%s…

向量数据库Milvus Cloud核心组件再升级,主打就是一个低延迟、高准确度

支持 ScaNN 索引 Faiss 实现的 ScaNN,又名 FastScan,使用更小的 PQ 编码和相应的指令集可以更为友好地访问 CPU 寄存器,从而使其拥有优秀的索引性能。该索引在 Cohere 数据集,Recall 约 95% 的时候,Milvus 使用 Knowhere 2.x 版本端到端的 QPS 是 IVF_FLAT 的 7 倍,HN…

JDBC入门到精通-10w总结

JDBC核心技术 笔记是以尚硅谷讲师宋红康JDBC课程为基础&#xff0c;加入自身学习体会&#xff0c;略有修改 第1章&#xff1a;JDBC概述 JDBC是java应用程序和数据库之间的桥梁。JDBC提供一组规范&#xff08;接口&#xff09;。向上是面向应用API&#xff0c;共应用程序使用。向…

磐基2.0搭建es集群

参考&#xff1a; k8s安装elasticsearch集群 k8s安装elasticsearch集群_k8s部署elasticsearch集群_MasonYyp的博客-CSDN博客1 环境简述搭建es集群需要使用的技术如下&#xff1a;k8s集群、StatefulSet控制器、Service&#xff08;NodePort&#xff09;服务、PV、PVC、volumeC…

数组的方法以及数组的创建以及概念

5.数组 递归 1.什么是递归? ​ 函数自己调用自己,要有临界点(结束条件) 1.数组的概念(复杂数据类型) 1.什么是数组它就是一组数据,js的数组成员可以是任意类型,它是可以动态扩容的2.数组的创建方式1).构造函数创建数组如果Array里有一个参数,并且是数值类型,表示数组的初始…

ForkJoinPool的使用及基本原理

文章目录 1. 简介2. ForkJoinPool的基本原理2.1 工作窃取算法&#xff08;Work Stealing&#xff09;2.1.1 工作窃取算法的定义和特点2.1.2 工作队列和双端队列的作用与区别 2.2 分治策略&#xff08;Divide and Conquer&#xff09;2.2.1 分治策略的概念和应用场景2.2.2 任务拆…

Linux中的软件管家——yum

目录 ​编辑 一&#xff0c;软件安装的方式 二&#xff0c;对yum的介绍 1.yum的作用 2&#xff0c;yum的库 三&#xff0c;yum下载软件的操作 1.yumlist 2.yuminstall 3.yumremove 四&#xff0c;yum源的转换 一&#xff0c;软件安装的方式 软件安装的方式大概分为三种…

AI消除笔快速去除脸部痘痘疤痕,新手变高手!

美颜的途径有很多&#xff0c;比较实用的是祛痘和柔肤。祛除人物脸部的痘痕有多种方法&#xff0c;比较常见且普遍的就是使用PS“污点去除”工具来去除痘痕&#xff0c;以及柔化皮肤。 除了PS我们还可以使用AI图片消除工具&#xff0c;这就对手残党非常的友好了。 牛学长图片…

IDEA快捷键第二版

1、选择当前行和上一行 按住 Shift键 再按两下向上键&#xff08; ↑ &#xff09;&#xff0c;按两下选两行&#xff0c;以此类推 2、将整个方法上移动 文本光标应放在方法的标头处&#xff0c;按住Ctrl Shift 向上键&#xff08; ↑ &#xff09;&#xff0c; 3、解包 …

JavaScript-----轮播图案例展示

前言&#xff1a; 这一期我们去通过JavaScript的代码实现轮播图的制作&#xff0c;下面有效果展示和代码资源&#xff0c;其中的图片资源和代码资源我都上传上去了&#xff0c;如果需要运行的话&#xff0c;你们可以去直接下载下来。希望各位喜欢&#xff01; 效果展示 16941…

外汇交易技巧分享:利用MT4交易平台进行精准的外汇技术分析

在外汇交易市场中&#xff0c;技术分析是一种重要的决策工具&#xff0c;能够帮助交易者预测价格走势和制定交易策略。而MT4交易平台作为一种功能强大、广泛应用的交易软件&#xff0c;为交易者提供了丰富的技术分析工具和功能。本文将与大家分享几个利用MT4交易平台(可在mtw.s…