土法炼钢兴趣小组的算法知识备份

【操作系统百科】用户态分配器:jemalloc vs tcmalloc

文章导航

分类入口
os
标签入口
#malloc#jemalloc#tcmalloc#allocator#arena#rseq#hugepage

目录

把 jemalloc 和 tcmalloc 放在一起看,真正值得关心的不是“谁更快”这类口号,而是它们分别把锁竞争、碎片、回收压力和调优复杂度放在了哪里。tcmalloc 的主线是:把最热的分配/释放路径尽量压进 per-CPU cache,用极少同步换多核扩展性;jemalloc 的主线是:把 arena、slab、extent、decay、profiling 组织成一套可预测、可观测、可调的内存管理体系。

如果应用是典型的长生命周期多线程服务,这个区别会直接反映到三个工程问题上:吞吐和尾延迟、RSS 与碎片、以及线上排障手段。本文只聚焦 jemalloc 与 tcmalloc,不展开 glibc ptmalloc、mimalloc 等其他路线。

一、先看图

flowchart LR
    subgraph T["tcmalloc"]
        T1[线程/CPU 上的 malloc/free] --> T2[Front-end<br/>per-CPU cache<br/>旧模式:per-thread cache]
        T2 -->|miss / overflow| T3[Middle-end<br/>Transfer Cache]
        T3 --> T4[CentralFreeList<br/>按 size class 管理]
        T4 --> T5[Back-end<br/>PageHeap / Hugepage-aware PageHeap]
        T5 --> T6[mmap / hugepage / OS]
    end

    subgraph J["jemalloc"]
        J1[线程上的 malloc/free] --> J2[tcache<br/>thread-specific cache]
        J2 -->|miss / flush| J3[arena]
        J3 --> J4[bin + slab<br/>small objects]
        J3 --> J5[extent<br/>large objects]
        J3 -. decay / purge .-> J6[madvise / OS]
        J4 --> J6
        J5 --> J6
    end

这张图已经把两者的气质差异画出来了:

二、共同地基:现代分配器到底在解决什么

jemalloc 和 tcmalloc 都不是在“优化一次 malloc() 调用”这么简单。它们共同要解决四个老问题:

  1. 小对象分配太频繁,不能每次都拿锁。
  2. 不同大小的对象混在一起会形成碎片。
  3. free 之后不等于 RSS 立刻下降。
  4. 多线程程序里,分配路径和释放路径常常不在同一个线程。

所以两者都用了三类共通手段:

共同手段 作用
size class 把任意大小请求映射到有限档位,避免每次做通用分割
本地 cache 让绝大多数小对象分配/释放走无锁或低锁快路径
页级回收 在对象层之上再做页/extent/span 级合并与返还

分歧不在“要不要这些东西”,而在本地 cache 是按线程还是按 CPU,中央结构按什么粒度组织,回收时优先追求吞吐、碎片边界还是 hugepage 完整性

三、tcmalloc:把热路径推进 per-CPU

3.1 三层结构是它的骨架

tcmalloc 官方设计文档把自己拆成三部分:

这种分层很重要,因为它意味着 tcmalloc 的核心优化思路不是“把全局结构做得更聪明”,而是尽量不碰全局结构

3.2 真正的关键:per-CPU cache

现代 tcmalloc 的默认快路径是 per-CPU mode,而不是早年的 per-thread mode。每个逻辑 CPU 都有自己的 cache 区域,里面按 size class 维护空闲对象指针数组。小对象分配时,如果本 CPU 的对应 size class 还有空闲对象,直接取走;释放时,如果本 CPU 缓存还没满,直接放回。

这条路径的关键不是“每 CPU 一份数据”本身,而是它在 Linux 上借助 restartable sequences(rseq) 实现更新:在最热的 push/pop 路径里,不需要原子指令,也不需要互斥锁;如果线程在临界片段中被抢占或迁移,序列会从头重启。

工程上可以把它理解成:

这就是 tcmalloc 在高并发小对象场景里经常表现很强的原因:它把“同步”从每次对象操作,降成了偶发的批量 refill / drain。

3.3 Middle-end 解决“跨 CPU 流动”

如果一个对象在 CPU 3 分配、在 CPU 11 释放,只靠 per-CPU cache 很容易把内存困死在错误的位置。tcmalloc 为此引入了 middle-end:

这层通常带锁,但因为是批量操作,锁成本被摊薄了。tcmalloc 的思路很明确:把锁留在批处理层,不要让锁进入每次对象分配。

3.4 Back-end 不只是在“拿页”,还在管 hugepage

tcmalloc 后端历史上是 pageheap;现代实现又增加了 hugepage-aware pageheap。官方 Temeraire 设计文档说明得很直白:它的目标之一,就是减少 pageheap 内部碎片,并提高 hugepage 保持完整的概率。

这条路线的含义是:

tcmalloc 还把自己的逻辑页大小做成了编译期选项(4 KiB、8 KiB、32 KiB、256 KiB)。这也是很典型的 tcmalloc 风格:把页级布局直接作为性能/空间旋钮暴露出来

3.5 tcmalloc 的代价

tcmalloc 不是白拿收益,它主要有三类代价:

  1. per-CPU cache 的占用随 CPU 数增长。 官方文档明确指出,机器 CPU 越多,可缓存的总内存也越多。
  2. 页大小与 release 策略没有免费午餐。 更大的逻辑页有利于聚集和 TLB,但也更容易形成“页面里只剩少量活对象”的悬挂空间。
  3. 更激进地释放内存会破坏 hugepage。 官方 tuning 文档明确提醒,过于积极地 ReleaseMemoryToSystem 会增加回填 fault,也会打碎 hugepage。

所以 tcmalloc 并不是“只看吞吐”的分配器;更准确地说,它是先把吞吐和多核扩展性做到极强,再通过 pageheap / hugepage 策略去把空间问题尽量补回来

四、jemalloc:把碎片与回收做成一等问题

4.1 多 arena + tcache 是它的基本盘

jemalloc 的官方 README 把目标写得很清楚:强调 fragmentation avoidance 和 scalable concurrency support。它当然也在做并发扩展,但切入点和 tcmalloc 不一样。

jemalloc 的实现笔记里明确写了两件事:

默认情况下,jemalloc 自动 arena 数是 CPU 数的 4 倍。这能有效降低竞争,但文档也同时强调了代价:每个 arena 有固定开销,而且 arena 彼此独立,会带来少量额外碎片。

这很能体现 jemalloc 的风格:它愿意接受可量化、可解释的额外结构成本,换取更稳定的并发和更可控的碎片行为。

4.2 jemalloc 的核心对象:bin、slab、extent

jemalloc 把内存概念化为 extents。在这个基础上:

small object 的分配方式尤其关键。jemalloc 的文档说明:

也就是说,jemalloc 的重点不是把 size class 做得“非常少”,而是把它做成一套更细密、更强调空间边界的分级体系。

4.3 jemalloc 真正拉开差距的地方:回收与治理

如果只看“有 arena、有 tcache、有 size class”,jemalloc 和 tcmalloc 好像只是术语不同。真正的分野在回收治理。

jemalloc 把这部分做成了一整套可调策略:

这些选项的共同指向非常明确:jemalloc 不满足于“能分配得快”,它希望把 RSS、返还节奏、oversize 隔离、虚拟地址复用都纳入同一套 mallctl 治理面。

4.4 可观测性是 jemalloc 的强项

jemalloc 在 2010 年以后把 heap profiling、统计和 mallctl*() 接口都做得很完整。对线上服务来说,这意味着两件事:

  1. allocator 内部状态本身是可读的。
  2. 调优动作可以比较精细地做,而不是只能换一个库重跑。

如果团队经常要回答“RSS 为什么没掉”“到底是 tcache、arena 还是大对象导致的”“哪类分配在拖高堆占用”这类问题,jemalloc 的工具化程度通常更顺手。

4.5 jemalloc 的代价

jemalloc 的代价也很明确:

  1. tcache 以线程为单位,会把一部分对象暂存在各线程本地。 文档明确指出,这会增加内存占用与碎片。
  2. arena 不是免费的。 arena 越多,竞争越低,但固定开销与独立管理导致的碎片也会上去。
  3. 调优面更大,意味着理解成本更高。 decay、background thread、retain、oversize、extent 行为都可能影响结果。

所以 jemalloc 的优势不在“默认一定最优”,而在当你真的在乎碎片边界、返还节奏和可观测性时,它给你的抓手更多。

五、并排对比:它们到底差在哪

维度 tcmalloc jemalloc
热路径目标 把绝大多数小对象操作压进 per-CPU cache 用 tcache 避开同步,再由 arena 承接 miss
本地 cache 归属 现代默认按 CPU;旧模式按线程 主要按线程
同步策略 快路径尽量不用锁,锁留给批量 refill / drain arena + bin 降低争用,快路径靠 tcache
中央结构 Transfer Cache + CentralFreeList + PageHeap 多 arena;每个 arena 管 bin/slab/extent
大对象管理 大于 kMaxSize 直接走 back-end large object 由独立 extent 支撑;oversize 可进专门 arena
hugepage 取向 很强,尤其是 hugepage-aware pageheap / Temeraire 有相关空间复用与 purge 策略,但设计重心不在 hugepage
碎片治理方式 靠 size class、pageheap、release、hugepage 布局来压 把 size class、oversize 隔离、decay、extent 复用做成系统化策略
可观测性 GetStats()、sampling、realized fragmentation mallctl*()malloc_stats_print()、heap profiling
典型风险 高 CPU 数下 cache 占用变大;过快 release 破坏 hugepage 高线程数下 tcache 占用增大;arena 过多会带来额外碎片

有一个很重要的判断:tcmalloc 更像“把 allocator 做成 CPU 本地化的数据面”,jemalloc 更像“把 allocator 做成可调的内存控制面”。

六、适用场景

6.1 更偏向 tcmalloc 的场景

下面这些情况,通常值得优先看 tcmalloc:

典型关键词是:高 QPS 服务、对象生命周期短、跨核竞争重、CPU 数多、堆占用大。

6.2 更偏向 jemalloc 的场景

下面这些情况,通常更适合先看 jemalloc:

典型关键词是:常驻进程、内存曲线波动大、线上经常要解释 RSS、希望把“调 allocator”当成一个正式运维手段。

6.3 两者都不该替你做的事

无论选哪个分配器,它们都不能替代下面这些工作:

  1. 对象生命周期设计。
  2. 业务层对象池 / region allocator / arena allocator。
  3. 峰值内存容量规划。

tcmalloc 官方文档明确提醒:释放速率只能让峰值之后的 footprint 回落,不能把峰值本身变小。这条对 jemalloc 也成立——allocator 能缓解碎片与回收抖动,但不能消灭业务层真实的峰值需求。

七、三个常见误判

7.1 “free 了,RSS 怎么还不掉?”

这通常不是 allocator 坏了,而是:

7.2 “切到更激进的 release,就一定更省内存”

不对。tcmalloc 的 tuning 文档明确指出,激进 release 会增加后续 fault 成本,还会打碎 hugepage。jemalloc 的 decay 也是同一个道理:回收更快,不代表全局更优,只是把空间换成了未来重用成本与抖动

7.3 “分配器选型只看 benchmark 排行”

这也是误区。用户态分配器最怕把单机 microbenchmark 当成全部真相,因为线上真正决定体验的往往是:

八、小结


参考资料

官方文档 / 源码

设计说明 / 文章


上一篇内核内存调试 下一篇VFS 四层抽象

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-05-01 · os

【操作系统百科】HugeTLB 与 THP

大页能让一条 TLB 覆盖 2MB 乃至 1GB,但 THP 为什么在数据库里默认关掉?本文讲 HugeTLB 预留池、THP 的 khugepaged、defrag stall、madvise 模式、file-backed THP、以及工程上的取舍。

2026-05-06 · os

【操作系统百科】内核内存调试

内核内存 bug 是最难追的:UAF、OOB、double free、leak 都可能沉默数月。本文讲 KASAN 三种模式、KFENCE 生产采样、kmemleak、SLUB_DEBUG、UBSAN/KCSAN 联动。

2026-05-08 · os

【操作系统百科】VFS 四层抽象

Linux 的一切皆文件靠 VFS 实现——superblock、inode、dentry、file 四层抽象加 ops 表。本文讲 VFS 核心数据结构、dcache、inode cache、RCU lookup,以及文件系统如何插入 VFS。


By .