如何分配一大段内存?

直接问操作系统要就好啦

  • MAP_ANONYMOUS 申请,想多少就有多少
    • 超过物理内存上限都行

反而,操作系统不支持分配一小段内存

  • 这是应用程序自己的事

malloc 和 free

Specification 很简单 (同 Lab1)

  • 在大区间 $[L, R)$ 中维护互不相交的区间的集合

$$ M = \big\{ [\ell_0, r_0), [\ell_1, r_1), \ldots, [\ell_n, r_n) \big\}$$

  • malloc($s$) - 返回一段大小为 $s$ 的区间
    • 必要时可以向操作系统申请额外的 $[L, R)$ (观察 strace)
    • 允许在内存不足时 “拒绝” 请求
  • free($\ell, r$) - 给定 $\ell$,删除 $[\ell, r) \in M$
    • 是否想起了《算法导论》?

多线程安全

  • Scalability 就是个很大的问题了

实现高效的 malloc/free

Premature optimization is the root of all evil.

——D. E. Knuth

重要的事情说三遍:

  • 脱离 workload 做优化就是耍流氓
  • 脱离 workload 做优化就是耍流氓
  • 脱离 workload 做优化就是耍流氓
    • 在开始考虑性能之前,理解你需要考虑什么样的性能

然后,去哪里找 workload?

Workload 分析

在实际系统中,我们通常不考虑 adversarial 的 worst case。

  • 指导思想:$O(n)$ 大小的对象分配后至少有 $\Omega(n)$ 的读写操作,否则就是 performance bug (不应该分配那么多)
    • 越小的对象创建/分配越频繁
      • 字符串、临时对象等;生存周期可长可短
    • 较为频繁地分配中等大小的对象
      • 较大的数组、复杂的对象;更长的生存周期
    • 低频率的大对象
      • 巨大的容器、分配器;很长的生存周期
  • 并行、并行、再并行
    • 所有分配都会在所有处理器上发生
    • 使用链表/区间树 (first fit) 可不是个好想法

malloc, Fast and Slow

设置两套系统:

  • Fast path
    • 性能极好、并行度极高、覆盖大部分情况
    • 但有小概率会失败 (fall back to slow path)
  • Slow path
    • 不在乎那么快
    • 但把困难的事情做好
      • 计算机系统里有很多这样的例子 (比如 cache)

人类也是这样的系统

  • Daniel Kahneman. Thinking, Fast and Slow. Farrar, Straus and Giroux, 2011.

malloc: Fast Path 设计

使所有 CPU 都能并行地申请内存

  • 线程都事先瓜分一些 “领地” (thread-local allocation buffer)
  • 默认从自己的领地里分配
    • 除了在另一个 CPU 释放,acquire lock 几乎总是成功
  • 如果自己的领地不足,就从全局的池子里借一点

不要在乎一点小的浪费

  • 这就是为什么要对齐到 $2^k$ 字节

小内存:Segregated List

分配: Segregated List (Slab)

  • 每个 slab 里的每个对象都一样大
    • 每个线程拥有每个对象大小的 slab
    • fast path → 立即在线程本地分配完成
    • slow path → pgalloc()
  • 两种实现
    • 全局大链表 v.s. List sharding (per-page 小链表)

回收

  • 直接归还到 slab 中
    • 注意这可能是另一个线程持有的 slab,需要 per-slab 锁 (小心数据竞争)

大内存:一把大锁保平安

Buddy system (1963)

  • 如果你想分配 1, 2, 3, 4, ... $n$ 个连续的页面?
    • 例如:64 KB/页面
  • 那就 first fit 或者 best fit 吧……

你只需要一个数据结构解决问题

  • 区间树;线段树……

现实世界中的 malloc/free

以上就是所有现代 malloc/free 实现的基础

  • 当然,实际情况会复杂一些,性能也是锱铢必较
    • glibc: arena → heap → tcache (thread-local)
    • tcmalloc: thread-caching malloc, mimalloc
    • OpenJDK: ZGC: region based + tlab (thread-local)
      • managed memory 允许 object move,因此复杂得多……