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.

Bash
多线程 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

Bash
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(有锁)

Bash
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+)

Bash
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

Bash
观察 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 缓存

Bash
高并发场景的问题:

如果线程数 > 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.


Last modified: 2024年2月6日

Author

Comments

Write a Reply or Comment

Your email address will not be published.