源码
https://github.com/dmtcp/dmtcp/tree/main/jalib
malloc 的特性和局限
malloc
/free
是操作系统(或 C 库)提供的通用堆分配器。
- 它通常会采用 “内存池 + 分块 + 空闲链表” 等技术,但它为了通用性和线程安全,设计得很复杂,开销较大。
- 在高并发 / 频繁小块分配释放的场景下,
malloc
的性能和碎片控制未必理想。
malloc
很难让你:
- 控制分配内存的位置(如 DMTCP 需要特殊内存区域)
- 统计 / 追踪所有分配的内存块
- 实现定制的分配策略(如无锁、分层小块池、预扩展等)
- 轻松调试和隔离内存问题
内存碎片
malloc 的确在其实现内部也维护着自己的 “内存池”,并且会对小块内存(small bins/tcache/fast bins 等)做优化和分组管理。比如在 glibc 的 malloc(ptmalloc)中,就有针对小块内存的快速分配机制。
malloc 是 “通用分配器”
malloc 需要支持所有应用场景,包括大 / 小 / 奇异尺寸的分配、跨多线程、兼容各种系统调用和 ABI。
为了兼容性和健壮性,malloc 实现复杂,包含很多额外的元数据和检查,导致分配 / 释放开销更大。malloc 的小块管理是 “全局的”
malloc 管理的小块是全进程共享的,所有线程 / 模块都会竞争同一套管理结构(如 fastbin、tcache、small bin)。
在高并发、频繁小块分配 / 释放的场景下,锁竞争和同步成本变高,可能成为性能瓶颈。自定义分配器(如 jalib)“更窄、更专用”
jalib 只服务 DMTCP 内部的特殊内存分配需求,只关注固定几种典型的小块尺寸(如 64/256/1024…)。
可以用更简单、更高效的 “无锁链表 + 内存对齐块” 来管理池,分配和释放几乎都是 O(1)的原子操作。
不需要兼容所有 malloc 的场景(如 realloc、跨模块释放等),所以能极致优化。控制权和可观测性
jalib 可以完全掌控池的生命周期、分配区域、分配策略(如预扩展、定制回收),还可以追踪统计、调试。
malloc 的内部状态你无法直接控制或感知,也无法方便地和 DMTCP 的 checkpoint、回滚等功能集成。内存碎片和确定性
专用分配器能保证分配块 “定长、对齐”,几乎无碎片,分配和回收都是确定性的。
malloc 需要兼容各种尺寸,碎片和内存抖动不可避免。
jalib(自定义分配器)的设计动机
性能优化
- DMTCP 频繁地分配和释放小块内存(如元数据、临时缓存等),如果每次都用 malloc,性能损耗大。
- jalib 采用分级固定块池,每次分配 / 释放只需操作链表和原子变量,比 malloc 更快、更少碎片。
线程安全的高效实现
- jalib 用无锁(128 位 CAS)或轻量级互斥方案,适合高并发分配 / 释放。
- malloc 虽然线程安全,但实现方式更重,适用范围更广,未必最优。
可控性和可追踪性
- jalib 可以统计分配次数、追踪所有内存池区,方便调试、分析和 checkpoint 恢复。
- 可根据实际需求预分配或批量扩展,避免运行时大规模内存抖动。
适应 DMTCP 的特殊需求
- DMTCP 需要在 checkpoint/restore 时管理所有内存区域,jalib 可以定制分配区域、分配方式,malloc 无法满足。
- 可实现特定平台的优化,如 mmap 固定地址分配等。
故障隔离和调试
- jalib 可以在有 bug 或内存泄漏时,帮助定位具体的分配 / 释放流程。
- 可以方便地记录所有 arena 信息,甚至实现特殊的调试模式。
总结
虽然 malloc 也是内存池管理,但它是为通用用途设计的,不能满足 DMTCP 这类高性能、高可控性、特殊内存管理需求场景。自定义 jalib 分配器可以更高效地管理小块内存,优化多线程性能,便于调试和适配特定需求。
可以归纳为三点:
- 性能更高,碎片更少
- 更好地适应 DMTCP 的需求
- 更易于调试和控制
jalloc 设计思路
多层级固定大小块分配(层级分配器)
- 设计了 5 个分配层级(lvl1~lvl5),每层负责不同大小的定长内存块(如 64、256、1024、4096、16384 字节)。
- 小于等于这 5 个等级的分配请求,会被分配到各自的层级。
- 超过最大层级的请求,则直接调用
_alloc_raw
(通常是mmap
)。
优点:
- 小块内存可以复用,减少系统调用和碎片。
- 大块内存仍可直接用系统接口,兼顾通用性。
固定块分配器 JFixedAllocStack
每个层级对应一个 JFixedAllocStack<N>
,其核心是无锁栈式管理:
- 内部维护一个空闲块栈(LIFO 链表)。
- allocate 时从栈取出一个空闲块,栈空时调用 expand 申请一批新块。
- deallocate 时将块归还到栈顶。
核心技术点
原子双字比较交换(128 位 CAS)
为了线程安全,栈顶指针 _top 需要原子更新。这里用到了 128 位 CAS(Compare-And-Swap),保证 node 指针和计数器同时更新,避免 ABA 问题。
CAS 不可用时的降级方案
对于不支持 128 位原子操作的平台,采用 futex+memcpy 的方式手动实现互斥和原子性。
线程安全设计
分配和释放都用原子操作保护,无需锁,性能高。
多线程环境下不会出现竞争条件或内存破坏。
Arena 记录和调试
分配的内存区域(arena)可以记录到全局数组中,方便调试和统计。
通过 JAlloc::getAllocArenas()
可获得分配区域列表。
全局 new/delete 重载(可选)
如果定义了 OVERRIDE_GLOBAL_ALLOCATOR
,会重载 operator new
和 operator delete
,让全局 new
/delete
也用这个分配器。
灵活切换
可以通过宏 JALIB_ALLOCATOR
切换:
- 启用时用自定义分配器
- 否则回退为标准 malloc/free
总结
本内存分配器的设计核心在于:
- 采用多级固定块内存池 + 无锁算法,高效服务于小块高频分配 / 释放;
- 通过 128 位原子操作或 futex 确保并发安全,适用多平台;
- 提供 arena 管理和统计,方便调试与维护;
- 兼容传统分配方式,易于集成和切换。
这种设计非常适合像 DMTCP 这样对性能和内存管理有特殊要求的系统级软件。