第四章:约束驱动结构 I——物理访问约束

6 阅读10分钟

第四章:约束驱动结构 I——物理访问约束

绪论:从算法结构到硬件现实

  • 前三章讨论的是 通用数据结构。这些结构主要关注三个问题:

    1. 数据如何存放
    2. 如何通过结构压缩搜索空间
    3. 如何通过计算直接定位数据

    这些结构的共同目标是 降低查找复杂度

  • 但在真实系统中,数据结构不仅受到 算法复杂度 的影响,还受到 硬件访问成本 的制约。

  • 计算机存储系统具有明显的层级结构:CPU Cache→主存(RAM)→ 磁盘(Disk)

  • 不同层级之间的访问延迟差异极大,访问成本可能相差 数十倍甚至数千倍

    因此,在工程系统中,数据结构的设计目标不再只是降低 理论复杂度,还必须 适应硬件访问模型

  • 围绕这些约束,系统逐渐形成了一类新的结构设计:

物理访问约束驱动的数据结构

  • 本章讨论四类典型约束:

    1. CPU Cache 约束
    2. 内存访问约束
    3. 磁盘随机 IO 约束
    4. 磁盘写入模式约束
  • 从本章开始,数据结构的设计将不再只由算法复杂度决定,而是由现实系统的 物理访问特性 所驱动。

  • 需要说明的是,本章不会逐一罗列所有相关数据结构,而是重点分析 典型结构扩展结构

    • 典型结构:完整展开,说明其核心机制
    • 扩展结构:只概括其优化方向与适用场景

一、CPU Cache 约束:缓存局部性

物理现实

  • CPU 访问主存时,通常按 Cache Line **(通常约 64B)**为单位加载数据。
  • 因此,一次内存访问通常不是只读取一个元素,而是读取其所在的一整段连续区域。

硬件结果

  • 连续访问 更容易命中缓存。
  • 离散访问 更容易触发 Cache Miss
  • 当结构中存在大量指针跳转时,CPU 很难利用已经加载到缓存中的数据。

工程思路

  • 减少指针跳转
  • 提高连续存储比例
  • 采用块化结构

常用结构

  1. 块化结构

    • 展开链表(Unrolled Linked List)
    • 压缩内存数组(Packed Memory Array)
  2. 分块字符串结构

    • 绳索结构(Rope)
    • 间隙缓冲区(Gap Buffer)
    • 片段表(Piece Table)

典型结构

展开链表(Unrolled Linked List)

核心思路

  • 在链表节点中存储 多个元素,减少指针跳转次数。

机制

  • 结构

    • 块内:连续数组存储多个元素
    • 块间:节点通过指针连接

特征

  • 缓存局部性提高
  • 局部更新成本较低
  • 仍保留链表的动态扩展能力

适用场景

  • 需要频繁遍历的链式结构
  • 需要提高缓存局部性的系统

压缩内存数组(Packed Memory Array)

核心思路

  • 在有序数组中 预留空隙,降低插入移动成本。

机制

  • 数据存储在连续数组中
  • 数组内部保留一定比例的空位

特征

  • 连续布局,缓存局部性良好
  • 插入成本低于普通有序数组

适用场景

  • 内存型有序结构
  • 需要兼顾查询效率与更新能力的系统

绳索结构(Rope)

核心思路

  • 将长字符串拆分为多个片段,以减少整体字符串复制。

机制

  • 叶节点存储字符串片段
  • 内部节点维护长度信息
  • 整体采用树形组织

特征

  • 拼接效率高
  • 减少大规模复制
  • 适合处理大型文本

适用场景

  • 文本编辑器
  • 长字符串处理系统

扩展结构

间隙缓冲区(Gap Buffer)

  • 优化方向:减少局部编辑成本。
  • 核心变化:在数组中维护可移动空隙,使插入操作在局部完成。
  • 适用场景:文本编辑器(如 Emacs)

片段表(Piece Table)

  • 优化方向:避免原始文本修改。
  • 核心变化:通过维护原始文本引用 来表示当前文档状态。
  • 适用场景:现代文本编辑器

二、内存访问约束:减少访问路径

物理现实

  • 主存(RAM)访问延迟远高于 CPU 运算。
  • 数据结构访问路径越长,需要的内存访问次数越多。

硬件结果

  • 深层结构会增加访问延迟
  • 查找路径越短,访问效率越高

工程思路

  • 控制结构高度
  • 限制访问路径长度
  • 保持动态平衡

常用数据结构

  1. 平衡搜索结构

    • 搜索树(AVL树、红黑树)
    • Treap(树堆)
    • Splay树(伸展树)
  2. 分层搜索结构

    • 跳表(Skip List)

典型结构

AVL树(AVL Tree)

核心思路

  • 通过严格平衡规则限制树高度。

机制

  • 任意节点左右子树高度差不超过 1
  • 插入或删除后通过旋转恢复平衡

特征

  • 查找复杂度稳定 O(log n)
  • 树高度严格受控
  • 更新维护成本较高

适用场景

  • 查询频繁,更新较少
  • 对延迟稳定性要求较高的结构

红黑树(Red-Black Tree)

核心思路

  • 通过 更宽松的平衡规则 降低更新维护成本。
  • 本质上是 2-3-4 树(2-3-4 Tree)的二叉表示
  • 一个黑节点及其相连的红节点,可以看作一个 多关键字节点

机制

  • 节点使用 红黑颜色规则 维护平衡

  • 在逻辑结构上:

    • 黑节点表示树的基本节点
    • 红节点表示该节点中的额外 Key
  • 插入或删除后,通过 旋转与重新着色恢复结构。

特征

  • 查找复杂度 O(log n)
  • 更新成本低于 AVL 树
  • 平衡约束较弱,结构更稳定

适用场景

  • 系统容器实现(如有序映射结构)
  • 动态数据集合
  • 需要频繁更新的有序结构

跳表(Skip List)

核心思路

  • 通过多级索引缩短查找路径。

机制

  • 底层为有序链表
  • 上层为稀疏索引链表

查找从最高层逐级下降。

特征

  • 平均复杂度 O(log n)
  • 结构简单
  • 支持并发实现

适用场景

  • 内存型索引结构
  • 并发系统

扩展结构

Treap(树堆)

  • 优化方向:通过随机优先级实现平衡。
  • 核心变化:将二叉搜索树与堆结构结合。
  • 适用场景:轻量级动态集合。

Splay Tree(伸展树)

  • 优化方向:利用访问局部性优化结构。
  • 核心变化:访问节点后将其旋转至根附近。
  • 适用场景:热点访问明显的系统。

三、磁盘随机 IO 约束:减少磁盘访问

物理现实

  • 磁盘读写以 磁盘页(Disk Page / Block) 为单位进行。
  • 一次随机访问通常需要 寻道 + 读取整页数据

因此即使只访问一个元素,系统也必须读取 整个磁盘页

硬件结果

  • 随机访问次数越多,系统性能越低
  • 每次磁盘访问都应尽可能读取 更多有用数据

工程思路

  • 将节点大小设计为接近磁盘页大小
  • 在一个节点中存储大量 Key
  • 提高树的扇出,降低树高度

这样一次磁盘读取即可获得 整页索引信息,从而减少访问次数。

常用数据结构

  1. 多路搜索树:

    • B树
    • B+树
    • B*树
  2. 缓存自适应结构:

    • Cache-Oblivious B-Tree(缓存无关B树)

典型结构

B树(B-tree)

核心思路

  • 将搜索树的 单 Key 节点 扩展为 多 Key 节点
  • 一个节点通常对应一个磁盘页,容纳多个 Key,从而一次读取获得更多索引信息。

机制

  • 节点内部存储多个 Key、对应数据(或数据引用)以及多个子节点指针。
  • 查找时,先在当前节点内部比较 Key,再进入对应子节点。

特征

  • 扇出大,树高度低
  • 适合磁盘访问
  • 插入删除可能触发分裂或合并

适用场景

  • 文件系统
  • 数据库索引

B+树(B+ Tree)

核心思路

  • 在 B树结构基础上,将 数据统一存储在叶节点
  • 非叶节点只存储 索引 Key 与子节点指针
  • 非叶节点可以看作一层 索引结构,用于引导查找路径。

机制

  • 内部节点只存储 Key 与子节点指针
  • 所有数据存储在叶节点
  • 叶节点之间形成 顺序链表(可实现范围查询)

特征

  • 树高度更低
  • 所有查找路径长度一致
  • 支持高效 范围查询

适用场景

  • 数据库索引
  • 磁盘型范围查询
  • B树家族的核心思想是:让一个节点尽可能装满一个磁盘页,从而用最少的磁盘访问完成查找。

扩展结构

B*树

  • 优化方向:提高 B+树 节点利用率。
  • 核心变化:节点满时优先与兄弟节点重新分配数据。
  • 适用场景:高密度磁盘索引结构。

Cache-Oblivious B-Tree

  • 优化方向:同时适配不同层级缓存,不依赖固定块大小。
  • 核心变化:使用递归布局组织节点,适配不同层级缓存。
  • 适用场景:跨层级存储优化。

四、磁盘写入模式约束:顺序写

物理现实

  • 磁盘顺序写远快于随机写。

硬件结果

  • 频繁随机写会严重影响写入性能。

工程思路

  • 将随机写转换为顺序写
  • 通过后台线程延迟整理结构

常用数据结构

  1. 写优化结构

    • LSM树
  2. LSM变体结构

    • Fractal Tree(分形树)
    • Bw-Tree

LSM树(Log Structured Merge Tree)

核心思路

  • 将随机写转换为顺序写,再通过后台合并恢复有序结构。

机制

  • 写入流程:

    1. 数据首先写入内存结构(MemTable)
    2. 当 MemTable 达到阈值时,刷写为磁盘文件(SSTable)
    3. 后台执行 合并(Compaction) ,整理多个 SSTable
  • MemTable 通常采用 跳表或红黑树

  • SSTable 是磁盘上的 不可修改有序表

特征

  • 写入性能高
  • 顺序写友好
  • 读操作可能需要查询多个 SSTable

适用场景

  • 日志系统
  • NoSQL 数据库

扩展结构

分形树(Fractal Tree)

  • 优化方向:降低写放大。
  • 核心变化:在LSM基础上,节点中加入缓冲区。
  • 适用场景:写入密集型数据库。

Bw-Tree

  • 优化方向:提高并发能力。
  • 核心变化:使用映射表与追加更新。
  • 适用场景:高并发数据库系统。

本章总结

前三章讨论的是 通用数据结构

而本章开始关注 硬件约束驱动的结构设计

四类约束对应四类结构演化:

  1. CPU Cache → 局部性结构
  2. RAM → 平衡结构
  3. 磁盘随机 IO → B树家族
  4. 磁盘顺序写 → LSM结构

此类数据结构的演化,本质上是 硬件访问约束驱动的结构优化

下一问题

物理访问约束解决的是 硬件访问效率问题

但在许多系统中,数据结构还需要满足另一类约束:操作顺序约束

例如:

  • 任务调度
  • 请求处理
  • 优先级管理

下一章讨论:操作顺序约束