Archipelago

Lec 7存储层级与缓存无关 B 树

讲义完成更新于 2026-09-17

Lec 7 存储层级与缓存无关 B 树(Memory Hierarchy & Cache-Oblivious B-Trees)

对应 MIT 6.851 Lecture 7 · 讲师:Erik Demaine · 核心文献:Aggarwal & Vitter (1988), Frigo et al. (FOCS 1999), Bender et al. (FOCS 2000 / SICOMP 2005)

TL;DR

  • 传统 RAM 模型忽略了现代计算机的多级存储层级与按块(Block)传输的物理现实,外存模型(DAM)与缓存无关模型(Cache-Oblivious)以最小化块传输次数(I/O)为目标。
  • 经典 B 树在已知块大小 BB 的外存模型下达成最优的 O(log⁡B+1N)O(\log_{B+1} N) 搜索与更新复杂度。
  • 缓存无关模型在代码无需感知硬件参数 BB 和 MM 的前提下,通过 van Emde Boas 空间填充递归布局、有序文件维护(OFM)与间接寻址(Indirection),同样达成了严格匹配经典 B 树的 O(log⁡B+1N)O(\log_{B+1} N) 传输界。

一、外存模型(External Memory Model / DAM)

外存模型由 Aggarwal 与 Vitter 于 1988 年确立,它将存储层级抽象为两级:

  • 容量为 MM 的高速内部缓存(分为 M/BM/B 个块);
  • 容量无限的慢速外部磁盘;
  • 数据以大小为 BB 的连续块为单位进行传输,单次块传输代价计为 1,CPU 本地计算免费。

1.1 基础操作复杂度界

  1. 扫描(Scanning): Scan(N)=O(⌈NB⌉)\text{Scan}(N) = O\left(\left\lceil \frac{N}{B} \right\rceil\right)
  2. 搜索(Searching): 使用分支度数为 Θ(B)\Theta(B) 的标准 B 树,每次节点检索恰好加载一个块,搜索传输代价为 O(log⁡B+1N)O(\log_{B+1} N)。根据信息论下界,确定元素在 NN 个已有元素中的插槽需要 Ω(log⁡N/log⁡B)=Ω(log⁡B+1N)\Omega(\log N / \log B) = \Omega(\log_{B+1} N) 次 I/O,故该搜索界是最优的。
  3. 排序(Sorting): 使用 M/BM/B 路外存归并排序,复杂度为: Sort(N)=O(NBlog⁡M/BNB)\text{Sort}(N) = O\left(\frac{N}{B} \log_{M/B} \frac{N}{B}\right)
  4. 缓冲树(Buffer Tree): 通过在内部节点设立缓冲区批量下发写操作,实现均摊 O(1Blog⁡M/BNB)O(\frac{1}{B} \log_{M/B} \frac{N}{B}) 的高效更新。

二、缓存无关模型(Cache-Oblivious Model)

Frigo、Leiserson、Prokop 与 Ramachandran(1999)提出缓存无关模型:

  • 算法使用标准 RAM 指针机指令编写,程序逻辑中绝对不允许出现硬件参数 MM 或 BB;
  • 块传输由操作系统/硬件缓存自动管理(基于离线最优替换或 LRU 策略);
  • 若算法对未知的任意 MM 和 BB 均达成最优 I/O,则它在系统的所有存储层级(L1/L2/L3/RAM/Disk)之间同时自动达到最优!

三、缓存无关 B 树的构造

在完全不知道块大小 BB 的情况下,如何设计能与经典 B 树匹敌的搜索树?Bender 等人给出了一套三步构造方案。

3.1 静态搜索:van Emde Boas 递归几何布局

在 NN 个元素上构建一棵完全二叉搜索树,重点在于将树节点映射到连续磁盘数组上的排列方式(vEB 布局):

  1. 将整棵高度为 h=log⁡2Nh = \log_2 N 的二叉树从正中间水平切开;
  2. 上半部分为一棵高度为 h/2h/2 的子树,包含约 N\sqrt{N} 个节点;
  3. 上半部分的叶子挂接约 N\sqrt{N} 棵下半分子树,每棵子树高度同样为 h/2h/2,大小约 N\sqrt{N};
  4. 递归对上半部分和所有下半部分子树应用 vEB 布局;在内存数组中,先连续存放上半部子树,随后依次连续存放各下半部子树。
                    ▲
                   / \  上半部 (高度 h/2, 节点数 √N)
                  /___\
                 /  |  \
                /   |   \
               ▲    ▲    ▲  下半部 (共 √N 棵子树, 每棵大小 √N)
              /__\ /__\ /__\

静态 vEB 布局搜索性能

在按 vEB 布局存储的完全二叉搜索树上执行前驱搜索,所需块传输次数严格为 O(log⁡BN)=O(log⁡B+1N)O(\log_B N) = O(\log_{B+1} N)。

证明精要: 考察递归展开过程中,子树大小首次满足 size≤B\text{size} \le B 的层级。 此时子树的高度 h′h' 满足 12log⁡2B<h′≤log⁡2B\frac{1}{2}\log_2 B < h' \le \log_2 B。 由于该子树在内存中连续存储且大小 ≤B\le B,从外存读取整棵子树最多跨越 2 个内存块(至多 2 次 I/O)。 从根到叶的搜索路径总高度为 log⁡2N\log_2 N,因此路径上最多穿过 log⁡2N12log⁡2B=2log⁡BN\frac{\log_2 N}{\frac{1}{2}\log_2 B} = 2 \log_B N 棵这样的子树。 总传输次数至多为 2×2log⁡BN=O(log⁡B+1N)2 \times 2 \log_B N = O(\log_{B+1} N)!整个分析对任意未知的 BB 完全成立!

3.2 动态化组件:有序文件维护(OFM)

为了支持动态插入与删除,必须避免每次更新都重写全数组:

  • 有序文件维护(Ordered File Maintenance, OFM):在大小为 O(N)O(N) 的静态数组中维护 NN 个元素的有序序列,允许元素之间保留常数大小的间隙(Gaps)。
  • 当在两元素间插入新键时,若局部稠密,OFM 通过在局部区间内均匀推挤重排,以均摊 O(log⁡2NB)O(\frac{\log^2 N}{B}) 次 I/O 重新分配间隙。

3.3 消除多余项:两级间接寻址(Indirection)

直接将 vEB 布局与 OFM 结合会导致插入耗时 O(log⁡BN+log⁡2NB)O(\log_B N + \frac{\log^2 N}{B})。当 BB 较小时,log⁡2NB\frac{\log^2 N}{B} 会成为瓶颈。 采用间接寻址技术消除该项:

  1. 将 NN 个元素划分为 Θ(N/log⁡N)\Theta(N / \log N) 个连续的小组,每个小组规模为 Θ(log⁡N)\Theta(\log N);
  2. 每个小组仅在内部维持一个简单的顺序数组,扫描或重排仅需 O(log⁡NB)=O(log⁡BN)O(\frac{\log N}{B}) = O(\log_B N) 次传输;
  3. 从每个小组中抽取一个代表元,送入顶层的 vEB + OFM 结构中(顶层仅含 N/log⁡NN / \log N 个元素);
  4. 只有当小组因多次插入分裂(或删除合并)时,才触发一次昂贵的顶层更新。因为小组增长需要 Ω(log⁡N)\Omega(\log N) 次操作,顶层开销被均摊吸收: O(log⁡2N/B)log⁡N=O(log⁡NB)=O(log⁡BN)\frac{O(\log^2 N / B)}{\log N} = O\left(\frac{\log N}{B}\right) = O(\log_B N)

最终,缓存无关 B 树实现了搜索、插入与删除均达到 O(log⁡B+1N)O(\log_{B+1} N) 的优异性能!

四、存储层级核心结论对照

操作类型外存模型(显式依赖 B,MB, M)缓存无关模型(参数未知)
数组扫描O(N/B)O(N/B)O(N/B)O(N/B)
元素搜索O(log⁡B+1N)O(\log_{B+1} N)(传统 B 树)O(log⁡B+1N)O(\log_{B+1} N)(vEB 布局)
动态增删O(log⁡B+1N)O(\log_{B+1} N)O(log⁡B+1N)O(\log_{B+1} N)(均摊,OFM+间接寻址)
无序排序O(NBlog⁡M/BNB)O(\frac{N}{B}\log_{M/B}\frac{N}{B})(M/BM/B 路归并)O(NBlog⁡M/BNB)O(\frac{N}{B}\log_{M/B}\frac{N}{B})(漏斗排序)

五、我的理解

经典 B 树的本质是“面向硬件参数硬编码”:如果磁盘块大小是 4KB,就把节点度数设为 512;换了固态硬盘或跨到 L2 缓存,原有的节点尺寸就立即失效。 缓存无关 B 树展示了一种具有分形美感的全新范式:

  1. vEB 布局在所有递归尺度上都是连续自相似的:它没有固定的“块”边界,而是在每一个可能的剖分深度都保持了树拓扑与存储地址的局部一致性;
  2. 无论真实硬件的 BB 是 64 字节(CPU 缓存线)还是 64KB(操作系统页),在这个连续的尺度谱系中,总能精准找到一个与该硬件 BB 大小相吻合的虚拟子树;
  3. 这种“以无限尺度的自相似性应对未知尺度”的数学结构,不仅解决了跨存储层级无需重编译的工程痛点,更将算法局部性分析推向了全新的高度。 :::