精读ptmalloc

ptmalloc 来自于 glibc 2.27 版本,以 64 位操作系统为例。

malloc

先贴出 malloc_chunk 的定义

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_chunk {

INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

其中 __libc_malloc 是 malloc 函数的原型。

查看更多

malloc_chunk 详解

malloc_chunk 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_chunk {

INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

已经分配的 chunk 格式如下:

s

查看更多

原理

内存管理不外乎三个层面,用户管理层、C运行时库层、操作系统层。我们一般使用 top 或者 /proc/pid/status 看到的是操作系统层的进程的内存占用。同时,可以通过代码来排查用户管理层的一些内存问题,比如内存泄漏之类。但是C运行时库的内存占用对于我们来说,排查问题很需要知道他的原理。我们需要解决如下问题:

  • Glibc 在什么情况下不会将内存归还给操作系统?
  • Glibc 的内存管理方式有哪些约束?适合什么样的内存分配场景?
  • Glibc 是如何管理内存的。
查看更多


title: 内存分配与释放概述

一、内存分配算法

分配算法概述,以 32 位系统为例

  • 小于等于 64字节,用 pool 算法分配
  • 64 到 512 字节之间,在最佳匹配算法分配和 pool 算法分配中取一种合适的
查看更多

jeprof 依赖 perl 环境,需要搭建 perl 环境。注意 perl 中有一些脚本使用的是绝对路径,需要放在绝对目录中

然后使用 jeprof 解析 heap 的时候,出现如下的问题:

1
2
3
4
5
6
7
8
9
jeprof /opt/WM_B/APP/HAVP/linux-arm/ANP/bin/pavaro ./custom_prof.mem_profiler.110003.20230307_114334_114347.heap
Using local file /opt/WM_B/APP/HAVP/linux-arm/ANP/bin/pavaro.
Argument "MSWin32" isn't numeric in numeric eq (==) at /bin/jeprof line 5124.
Argument "linux" isn't numeric in numeric eq (==) at /bin/jeprof line 5124.
Using local file ./custom_prof.mem_profiler.110003.20230307_114334_114347.heap.
Welcome to jeprof! For help, type 'help'.
(jeprof) top20
Total: 41.4 MB
41.4 100.0% 100.0% 41.4 100.0% 0000ffff81627978

解决第一个问题,jeprof 使用了 file,并且是 /usr/bin/file 这个路径下的 file。注意 file 命令需要设置 magic file。

查看更多

jemalloc 的参数和优化思路

一、参数

1
2
3
export MALLOC_CONF="narenas:8,dirty_decay_ms:5000,muzzy_decay_ms:5000"
export MALLOC_CONF="narenas:16,dirty_decay_ms:5000,muzzy_decay_ms:5000,junk:false"

  • backgroud_thread:启用/禁用内部后台工作线程。当设置为 true 时,将根据需要创建后台线程(后台线程的数量不会超过 CPU 或活跃 arena 的数量)。线程定期运行,并且异步进行清理操作

查看更多

prof 配置的初始化路径

1
2
3
malloc_init() 会被 constructor 调用
malloc_init() -> malloc_init_hard() -> malloc_init_hard_a0_locked() -> ... -> malloc_conf_init_helper()
初始化 opt_prof

内存分配流程

1
2
C 库内存管理函数 -> imalloc() -> imalloc_body() 
如果 config_prof 和 opt_prof 都为 true,则 prof 打开 -> 检测是否需要采样 -> 调用 imalloc_no_sample() / imalloc_sample() 两种逻辑

查看更多

原理

一、核心概念

1. arena

arena 是 jemalloc 最重要的部分,内存由一定数量的 arenas 负责管理。每个用户线程都会被绑定到一个 arena 上,线程采用 round-robin 轮询的方式选择可用的 arena 进行内存分配,为了减少线程之间的锁竞争,默认每个 CPU 会分配 4 个 arena。

2. bin

bin 用于管理不同档位的内存单元,每个 bin 管理的内存大小是按分类依次递增。因为 jemalloc 中小内存的分配是基于 Slab 算法完成的,所以会产生不同类别的内存块。

3. chunk

chunk 是负责管理用户内存块的数据结构,chunk 以 Page 为单位管理内存,默认大小是 4M,即 1024 个连续的页。每个 chunk 可被用于多次小内存的申请,但是在大内存分配的场景下只能分配一次。

查看更多

编译 jemalloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 需要在编译时指定 `--enable-prof` 参数,`--enable-prof-libunwind` 打开 libunwind 堆栈。

mkdir build
cd build
../configure --prefix=/usr/local/jemalloc_5.2.1 --enable-debug --enable-prof --enable-log
make -j4
sudo make install

# 编译 aarch64
mkdir build_aarch64
cd build_aarch64
../configure --prefix=/usr/local/jemalloc_5.2.1 --host=aarch64-linux-gnu --target=aarch64-linux-gnu --enable-debug --enable-prof --with-lg-page=16 --enable-prof-libunwind
make -j10

--enable-prof-libunwind: 打开 libunwind 堆栈
--with-lg-page=16 设置 page size 为 65536。用于交叉编译

--with-lg-page=<lg-page> 指定系统页面大小,以 2 为基数。此选项仅用于交叉编译
--with-lg-page-size=<lg-page-size>

问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
一、 交叉编译后,在 arm 上使用出现 <jemalloc>: Unsupported system page size
可能是因为在 x86 上编译的 jemalloc 使用了 x86 特有的页面大小,而在 aarch64 上使用时无法识别.
x86 上 getconf PAGE_SIZE. 得到 4096
aarch64 上使用 $ python -c 'import os; print(os.sysconf("SC_PAGE_SIZE"))' 得到 65536
因此,使用 --with-lg-page=16 指定系统页面大小。解决此问题

二、想要增加 libunwind.so 来进行获取堆栈
增加环境变量
export CPPFLAGS="-I/data/App/toolchain/target/out_aarch64/libunwind-1.5.0/include"
export LDFLAGS="-L/data/App/toolchain/target/out_aarch64/libunwind-1.5.0/lib -Wl,-rpath=/data/App/toolchain/target/out_aarch64/libunwind-1.5.0/lib"

使用 libunwind.so 1.6.2 版本,会 core
换成 libunwind.so 1.1 版本,不行,不支持 aarch64
换成 libunwind.so 1.5.0 版本,可以。**注意要把生成的 unwind/bin 下的所有 lib 都拷贝到指定环境 **
现在看起来生成的 heap 文件,使用 jeprof 可以解析出堆栈信息了。但是这些信息并没有被解析成符号
是因为 addr2line 这个二进制没有的缘故,加上即可。现在 jeprof 一些基本选项可以使用

三、对于 jeprof 的一些其他选项,比如 --svg
需要 Graphviz 的 dot 这些工具。交叉编译 Graphviz 然后使用 dot 即可
dot 在使用时,发现使用不正常,lib 文件放的位置不正确,需要调整。

查看更多