A03 — Multi-threaded malloc: Arena Contention and Per-thread Arena
Key Insight: In multi-threaded scenarios, every malloc is a potential contention point. ptmalloc uses arenas to solve this problem.
The Problem with Multi-threaded malloc
In single-threaded programs, malloc contention is rare (only one heap). But in multi-threaded programs, all threads share the same heap. Without special handling, severe lock contention occurs.
多线程 malloc 的问题:
线程 A:malloc(100) → 加锁 → 分配 → 解锁
线程 B:malloc(200) → 等锁 → 等锁 → 分配 → 解锁
线程 C:malloc(300) → 等锁 → 等锁 → 等锁 → 分配 → 解锁
问题:
- 锁竞争严重(所有线程争一把锁)
- 上下文切换开销大
- 分配变成瓶颈(CPU 浪费在等锁上)
解决:per-thread arena(每个线程有自己的 arena)
线程 A:malloc(100) → 从自己的 arena 分配 → 无锁!
线程 B:malloc(200) → 从自己的 arena 分配 → 无锁!
线程 C:malloc(300) → 从自己的 arena 分配 → 无锁!The Concept of Arena
Arena = 内存区域 + bin 结构 + 锁
glibc 的 arena 策略:
1. Main arena:
- 主线程使用
- 通过 brk 扩展堆
- 有一把锁(所有线程竞争)
2. Per-thread arena:
- 其他线程使用
- 通过 mmap 分配自己的 arena
- 可能有多把锁(减少竞争)
Arena 的数量限制:
- 32-bit:arena 数量 = CPU 核心数 × 2 + 1
- 64-bit:arena 数量 = CPU 核心数 × 8 + 1
超过限制后,新线程复用已有 arena(有锁)Arena 的结构(简化):
struct malloc_state {
mutex_t mutex; // 锁
int max_fast; // fastbin 大小上限
size_t top_chunk; // top chunk
// fastbins(无锁)
mfastbinptr fastbinsY[NFASTBINS];
// smallbins + largebins(需要锁)
malloc_chunk *bins[2 * NBINS];
// unsorted bin(需要锁)
malloc_chunk *bins[2];
};
每个 arena 都有自己的 bin 结构
线程优先用自己的 arena,只有满了才用其他的Thread Cache(glibc 2.26+)
Tcache(Thread Cache)= per-thread 的快速缓存
glibc 2.26 引入了 tcache,进一步减少锁竞争:
struct tcache_perthread_state {
unsigned int counts[TCACHE_MAX_BINS];
char *entries[TCACHE_MAX_BINS]; // 每种大小的链表头
};
TCACHE 的设计:
- 每个线程有自己的 tcache(无锁)
- 大小类别:32, 64, 96, 128, 192, 256, ... 1036 bytes(max 64 类)
- 每个类别最多 7 个 chunk(限制)
- 超过 7 个,放入 arena 的 bin(有锁)
分配流程(新版 glibc):
1. 检查 size 对应的 tcache
2. 如果 tcache 有 → 无锁分配(O(1))
3. 如果 tcache 空 → 进入 arena(有锁)
4. 或者合并 fastbin 到 tcache,再从 tcache 取
Tcache 的优势:
- 大部分分配(几十字节~几百字节)完全无锁
- 性能大幅提升(接近 tcmalloc)Actual Impact of Arena Contention
观察 arena 竞争:
$ cat /proc/$(pidof myprogram)/maps | grep "[heap]"
00...-00... [heap] ← main arena(主堆)
00...-00... [heap] ← thread arena 1
00...-00... [heap] ← thread arena 2
...(多个 [heap] 区域)
每个 [heap] 是一个独立的 arena(mmap 分配)
Arena 数量计算(8-core 64-bit 系统):
max arenas = 8 × 8 + 1 = 65
性能对比(多线程):
- 无 tcache:锁竞争严重,malloc 耗时 100~500ns
- 有 tcache:大部分无锁,malloc 耗时 10~20ns
- tcmalloc:类似的 thread local 缓存高并发场景的问题:
如果线程数 > max arenas:
- 新线程复用已有 arena
- 锁竞争重新出现
- 性能可能比少线程更差
解决方案:
1. 减少线程数
2. 使用 tcmalloc/jemalloc(更高效的 arena 管理)
3. 使用 mmap_large(减少 mmap 调用)
perf 观察:
$ perf stat -e lock:internal_lock_acquired ./program
观察锁竞争事件的次数Summary
- Arena Contention: Multi-threaded shared heap → all threads compete for one lock → poor performance
- per-thread arena: Each thread has its own arena (reduces lock contention)
- Arena Count Limit: 32-bit = cores x 2 + 1, 64-bit = cores x 8 + 1
- Tcache (glibc 2.26+): Per-thread fast cache; 64 size classes; max 7 chunks/class; completely lock-free
- Allocation Flow: tcache → arena → top chunk → brk/mmap
Next (A04): What is a memory leak? How malloc without free causes leaks, and how to detect and prevent them.
Comments