1. Jemalloc简介
jemalloc 是由 Jason Evans 在 FreeBSD 项目中引入的新一代内存分配器。它是一个通用的 malloc 实现,侧重于减少内存碎片和提升高并发场景下内存的分配效率,其目标是能够替代 malloc。jemalloc 在 2005 年首次作为 FreeBSD libc 分配器使用,2010年,jemalloc 的功能延伸到如堆分析和监控/调优等。现代的 jemalloc 版本依然集成在 FreeBSD 中。
jemalloc 应用十分广泛,在 Firefox、Redis、Rust、Netty 等出名的产品或者编程语言中都有大量使用。具体细节可以参考 Jason Evans 发表的论文 《A Scalable Concurrent malloc Implementation for FreeBSD》。本次移植的jemalloc版本是5.2.1.2版本
2. Jemalloc基础知识
以下是jemalloc一些基本数据结构的介绍,当然网上也有对应的解释,可以对照这看,
2.1 size_class
在jemalloc中,size class表示不同大小的内存,每个size_class 代表jemalloc分配的内存大小,共有SC_NSIZES(232)个,这里的232个是通过计算得出的。size class中有一个group(组)的概念,一般情况下每个组中含有4个size class,对于group0 比较特殊,就是1个,这里有三个组别:ps:这个系统内存对齐是指栈内存对齐
1:tiny group,这个表示的最小分配的内存,在jemalloc中规定最小分配的内存是8字节,也就说,malloc分配的字节数小于等于8字节的,分配的内存就是八字节,这组中size_class个数是由系统运行时内存对齐方式指定,正常情况,系统是指令一般是16字节对齐,所以这个组的size class的个数是1,
2:pseudo group :这个组中的size_class 是4个,但是每个size class 的计算规则与regular,这里是根据系统内存对齐大小为间距,计算出每一个size_class内存的大小,因为系统内存对齐正常是16字节,所以这里4个size class分别为1*16,2*16,3*16,4*16,共四个size class
3:regular group:剩余的232-4-1=227个size class 都是这组中的,这里的计算规则如下:在2^n到2^(n+1)之间的4个size class算一个组,左开右闭,相邻的size class之间的间距是2^(n-2),因此每个regular group中的size class的计算方式是 5*2^(n-2),6*2^(n-2),7*2^(n-2),8*2^(n-2),现在正常的系统都是64位系统,所以最大能够分配的内存上2^63,所以这里的最大值是63-1=62,所以regular group 组的个数是62-6+1=57,每个组中有4个size class,故regular group中size class 的个数是57*4-1=227,因为2^63内存正常取不到,故去掉。
对于size class ,jemalloc也将其分为两类:
small_class (小内存):对于64位机器来说,通常区间是[8,14K]共36个size class,这些内存一般会有一些缓存,能够快速的分配给应用程序,small_class 在代码中也称作bin。
large_class(大内存): 对于64位机器来说,通常区间是 [16kB, 7EiB],从 4 * page_size 开始,常见的比如 16KB, 32KB, …, 1MB, 2MB, 4MB,最大是 $2^{62}+3^{60}$,对于64位操作系统,页大小为4kB
每个size class 都有对应的index号 称为size_index,区间为 [0,231],比如8字节则为0,14字节(按16计算)为1,4kb字节为28,当 size 是 small_class 时,size_index也称作binind,
对于size_class这块的具体内容,大家也可以去看源码中sc.c和sc.h两个文件,里面有相关的规则和注释
2.2 arena
arena 是 jemalloc 最重要的部分,内存由一定数量的 arenas 负责管理。每个用户线程都会被绑定到一个 arena 上,线程采用 round-robin 轮询的方式选择可用的 arena 进行内存分配,为了减少线程之间的锁竞争,默认每个 CPU 会分配 4 个 arena,各个 arena 所管理的内存相互独立。对应的数据结构存放在源码的include/jemalloc/internal/arena_structs.h文件中
struct arena_s {
atomic_u_t nthreads[2]; /*当前arena绑定的线程数,0表示应用程序分配,1表示内部元数据(metadata)分配*/
atomic_u_t binshard_next;
tsdn_t *last_thd;
arena_stats_t stats; /* Synchronization: internal. */
//以下两个变量主要是用于数据统计
ql_head(tcache_slow_t) tcache_ql;
ql_head(cache_bin_array_descriptor_t) cache_bin_array_descriptor_ql;
malloc_mutex_t tcache_ql_mtx;
atomic_u_t dss_prec; //这个表示从系统分配内存的时候是否开启brk或sbrk优选分配
edata_list_active_t large; /*Extant large allocations.*/
malloc_mutex_t large_mtx; /* Synchronizes all large allocation/update/deallocation. */
pa_shard_t pa_shard; /* The page-level allocator shard this arena uses. */
unsigned ind; 关联base index
base_t *base; 内部数据分配器
nstime_t create_time; /* Used to determine uptime. Read-only after initialization. */
bin_t bins[0]; //arena中缓存small_class 内存的个数以及相关信息
};
这里需要说明变量:
bin[0],它是一个bin_t数据结构的指针,它指向的是41个bin_t结构的数组,在jemalloc中,能够预分配在arena中的最大内存是2^15,这里预分配的意思是,当用户分配到指定大小的内存时,jemalloc会根据对应的bin_info_t中信息,分配指定个数指定大小的内存。例如:当用户第一次分配16字节内存时,jemalloc会一次性分配200个16字节的内存放到bin[1]中,用于用户使用。而在2^15字节以上内存,就没有这种预分配。
pa_shard_t:这个数据结构用于arena分配PAGE以上的内存,具体的分配规则后面在内存分配时会详细说明;同时内存释放时候,该结构体中也会有对应的操作,在内存释放的时会详细说明