jemalloc内存分配器

发布时间 2023-11-21 15:59:43作者: qt-小趴菜

1. Linux内存分配

 一个进程的地址空间中,包含了静态内存、以及动态内存(常说的堆栈),栈的动态分配和释放由编译器完成,对于堆上内存,Linux 提供了 brk、sbrk、mmap、munmap 等系统调用来进行内存分配和释放,但是这些函数的直接使用会带来不小的理解门槛和使用复杂性,如 brk 需要指定堆的上界地址,容易出现内存错误;mmap 直接申请 pagesize 为单位的内存,对于小于此内存的分配会造成极大的内存浪费。因此需要有内存分配器来辅助管理堆的动态申请和释放。
内存碎片
虽然内存分配器在一定程度上保证了内存的利用率,但是不可避免地会出现内存碎片,包括了内存页内碎片和内存页间碎片,碎片的产生会导致部分内存不可用,内存碎片的大小也是评估一个内存分配器好坏的重要指标。

2. 常见的内存分配管理算法

堆上内存以链式形式存在,最简单的动态内存分配算法有:
First fit:寻找找第一个满足请求 size 的内存块做分配
Next fit:从当前分配的地址开始,寻找下一个满足请求 size 的空闲块
best fist:对空闲块进行排序,然后找第一个满足要求的空闲块
另外还有 Buddy 算法和 Slab 算法,也是 jemalloc 中用到的核心算法:

Buddy allocation
Buddy 算法,一般 2 的 n 次幂大小来管理内存,当申请的内存 size 较小,且当前空闲内存块均大于 size 的两倍,那么会将较大的块分裂,直到分裂出大于size,并小于 size * 2的块为止;当内存 size 较大时则相反,会将空闲块不断合并。
Buddy 算法没有块间内存碎片,但是块内内存碎片较大,可以看到当申请 2KB+1B 的 size 时,需要用 4KB 的内存块,内存碎片最坏情况可达 50%。

Slab allocation
调用 Linux 系统调用进行内存的分配和释放会让程序陷入内核态,带来不小的性能开销,slab 算法应运而生。每个 slab 都是一块连续内存,并将其划分成大小相同的 slots,用 bitmap 来记录这些 slots 的空闲情况,内存分配时,返回一个 bitmap 中记录的空闲块,释放时,将其记录忙碌即可,而 slab size 和 slot size 是内存碎片大小的关键。

3. Jemalloc 主要有以下几个特点

JeMalloc 是一款内存分配器,与其它内存分配器相比,它最大的优势在于多线程情况下的高性能以及内存碎片的减少。

高效地分配和释放内存,可以有效提升程序的运行速度,并且节省 CPU 资源
尽量少的内存碎片,一个长稳运行地程序如果不控制内存碎片的产生,那么可以预见地这个程序会在某一时刻崩溃,限制了程序的运行生命周期
支持堆的 profiling,可以有效地用来分析内存问题
支持多样化的参数,可以针对自身地程序特点来定制运行时 Jemalloc 各个模块大小,以获得最优的性能和资源占用比

4. 使用

下载jemalloc-5.3.0

#cd jemalloc
#./autogen.sh
#make -j 6
#make install

官方使用教程  jemalloc wiki 

1).查找内存损坏错误
从jemalloc 5.0开始,已删除与valgrind的集成。如果jemalloc是用--enable-debug指定配置的,则将编译各种断言以检测双精度释放,未对齐的指针等。这些检查对于正确运行应用程序不是必需的,但在开发过程中可能会有所帮助。禁用线程缓存(MALLOC_CONF=tcache:false)往往会使jemalloc的内部断言在捕获应用程序错误(尤其是两次释放)时更加有效。
如果jemalloc是使用--enable-fill指定的配置的,则可以使用MALLOC_CONF=junk:true在环境中进行设置,以告知malloc()用0xa5字节填充对象,并用free()释放内存。
2).基本分配器统计
默认情况下,jemalloc使用线程本地存储进行对象缓存。对于前面使用的ex_stats_print.c示例代码,运行MALLOC_CONF=stats_print:true ./ex_stats_print.out就能知道jemalloc在内部实际响应应用程序的内存分配活动在做什么,以改进分配器。

3). 使用mallctl*()自我检查
对于以下ex_mallctl.c示例代码:

#include <stdlib.h>
#include <jemalloc/jemalloc.h>

void do_something(size_t i) {
        // Leak some memory.
        malloc(i * 100);
}

int main(int argc, char **argv) {
        for (size_t i = 0; i < 1000; i++) {
                do_something(i);
        }

        // Dump allocator statistics to stderr.
        malloc_stats_print(NULL, NULL, NULL);

        return 0;
}

4)/ 泄漏检查

jemalloc的堆配置文件可以帮助检测应用程序中是否发现内存泄漏。jemalloc的堆配置文件输出文件是gperftools创建的文件的功能超集,关于gperftools堆配置文件的使用在:https://github.com/gperftools/gperftools/,这里不做安装和使用说明。
使用如下命令来查看程序退出时分配了什么内存:

MALLOC_CONF=prof_leak:true,lg_prof_sample:0,prof_final:true LD_PRELOAD=/root/Download/jemalloc/lib/libjemalloc.so.2 w

要生成发生泄漏的调用图的PDF,请运行:

jeprof --show_bytes --pdf `which w` jeprof.19678.0.f.heap > w.pdf

5)堆分析
除了以上能在出口进行泄漏检查外,jemalloc还能转储配置文件。转储到指定的文件名,MALLOC_CONF中将prof设置为true:

export MALLOC_CONF="prof:true,prof_prefix:jeprof.out"

对于以下prof.c示例代码,编译运行之后便能看到结果:

#include <stdio.h>
#include <jemalloc/jemalloc.h>
int main()
{
     const char *fileName = "heap_info.out";
     mallctl("prof.dump", NULL, NULL, &fileName, sizeof(const char *));
     return 0;
}

 



参考资料:

https://zhuanlan.zhihu.com/p/48957114

https://blog.csdn.net/qq_36287943/article/details/105491301/