Files
final-exam/计算机系统结构/复习文档/存储系统与Cache.md
T

14 KiB
Raw Blame History

tags, create time
tags create time
计算机系统结构
复习
Cache
2026-06-15 10:00

存储系统与 Cache

概述

本文档系统讲解存储层次结构、Cache 的工作原理(映像方式、替换策略、写策略)、命中率计算以及 17 种 Cache 优化技术。Cache 是计算机系统结构的核心考点,几乎每年都有大分值的计算和分析题。

[!tip] 考试重点 Cache 命中率计算、平均访存时间计算、分离 Cache vs 混合 Cache 的性能对比是必考内容。17 种优化技术的分类和代表性技术需熟记。

正文

一、存储层次结构

graph TD
    A["Register"] --> B["L1 Cache"]
    B --> C["L2 Cache"]
    C --> D["Main Memory"]
    D --> E["Disk/SSD"]

    style A fill:#ff8a80
    style B fill:#ff80ab
    style C fill:#ea80fc
    style D fill:#82b1ff
    style E fill:#80d8ff
层次 容量 速度 价格/位 管理方式
寄存器 KB 最快 最贵 编译器
L1 Cache 32~64 KB ~1 ns 较贵 硬件
L2 Cache 256 KB~1 MB ~5 ns 中等 硬件
主存 4~64 GB ~100 ns 便宜 OS+硬件
磁盘 TB 级 ~10 ms 最便宜 OS

[!question] 为什么存储系统要分层? 单一存储器无法同时满足"大容量、高速度、低价格"的要求。分层设计利用局部性原理,将频繁访问的数据放在快速但小的上层,不常用的放在慢速但大的下层,以较低成本获得接近最快速度的平均性能。

二、Cache 基本原理

2.1 映像方式

映像方式 原理 优点 缺点
直接映像 主存块只能映射到 Cache 的固定位置 简单、命中时间短 冲突不命中率高
全相联 主存块可映射到 Cache 任意位置 冲突不命中率最低 硬件开销大、查找慢
组相联 折中方案,分为 2^n 组,组内全相联 平衡性能和开销 复杂度适中

[!note] 组相联 Cache 的命名 "n 路组相联"表示每组有 n 个 Cache 行。2 路组相联 = 每组 2 行,4 路组相联 = 每组 4 行。

地址解析格式:

处理器发出的地址需要被拆分为 Tag / Index / Offset 三部分,不同映像方式的拆分规则不同:

graph LR
    subgraph "直接映像地址"
        A1["Tag"] --- A2["Index"] --- A3["Block Offset"]
    end
    subgraph "全相联地址"
        B1["Tag"] --- B2["Block Offset"]
    end
    subgraph "组相联地址"
        C1["Tag"] --- C2["Set Index"] --- C3["Block Offset"]
    end

各字段含义:

字段 含义 位数计算
Block Offset 块内偏移,定位块中具体哪个字节 \log_2(\text{块大小})
Index(直接映像) 定位 Cache 中的哪一行 \log_2(\text{Cache 行数})
Set Index(组相联) 定位 Cache 中的哪一组 \log_2(\text{组数})
Tag 标记位,用于比对确认是否命中 地址总位数 - Index位数 - Offset位数

[!example] 地址位分解示例

已知:32 位地址,Cache 容量 16KB,块大小 64B,采用 4 路组相联。

计算各字段位数:

  • Offset 位数 = \log_2(64) = 6 位
  • Cache 行数 = 16\text{KB} / 64\text{B} = 256 行
  • 组数 = 256 / 4 = 64 组
  • Set Index 位数 = \log_2(64) = 6 位
  • Tag 位数 = 32 - 6 - 6 = 20 位

地址 0x1A2B3C47 的解析:

字段 位数 二进制值
Tag(高 20 位) 31~12 0001 1010 0010 1011 0011
Set Index(6 位) 11~6 110001 = 第 49 组
Offset(6 位) 5~0 000111 = 块内第 7 字节

[!question] 如果改成直接映像呢? 直接映像:Index = \log_2(256) = 8 位,Tag = 32 - 8 - 6 = 18 位。 同一地址会映射到第 10010001 = 第 145 行,而不是第 49 组的某一行。 相联度越高,Index 位数越少,Tag 位数越多,硬件比对逻辑越复杂。

2.2 替换策略

策略 原理 特点
LRU(最近最少使用) 替换最久未被访问的块 最常用,效果好
FIFO 替换最早进入的块 简单,但可能替换活跃块
随机 随机选择替换块 实现简单,效果尚可

2.3 写策略

策略 命中时 不命中时 一致性
写直达 同时写 Cache 和主存 Write-Allocate 或 No-Write-Allocate 简单但总线流量大
写回 只写 Cache,标记为脏 Write-Allocate 复杂但总线流量小

三、Cache 性能计算

3.1 命中率与不命中率

\text{不命中率} = w_I \times mr_I + w_D \times mr_D

其中 $w_I$、w_D 分别为指令和数据的访问占比,$mr_I$、mr_D 为各自的不命中率。

[!example] 完整例题:地址序列命中/不命中判断

已知:直接映像 Cache,4 行,块大小 16B(4 个字,每字 4B)。地址按字节编址。

地址结构:[ Tag(高位) | Index(2位) | Offset(4位) ]

  • Index 位数 = \log_2(4) = 2 位(4 行直接映像)
  • Offset 位数 = \log_2(16) = 4 位(块大小 16B)

访问以下地址序列(十进制,假设地址按字节编址):

序号 地址(十进制) 地址(二进制) Tag Index Offset 块号 行号 结果
1 0 00000000 0 00 0000 0 0 不命中(冷启动)
2 4 00000100 0 00 0100 0 0 命中(同一块)
3 16 00010000 0 01 0000 1 1 不命中(新行)
4 132 10000100 8 01 0100 8 1 不命中(替换行 1)
5 136 10001000 8 01 1000 8 1 命中(同一块)
6 64 01000000 4 00 0000 4 0 不命中(替换行 0)
7 48 00110000 3 00 0000 3 0 不命中(替换行 0)
8 64 01000000 4 00 0000 4 0 不命中(已被替换)

Cache 行状态变化:

步骤 行 0 行 1 行 2 行 3
初始 空 空 空 空
访问 0 Tag=0 空 空 空
访问 4 Tag=0 空 空 空
访问 16 Tag=0 Tag=0 空 空
访问 132 Tag=0 Tag=8 空 空
访问 64 Tag=4 Tag=8 空 空
访问 48 Tag=3 Tag=8 空 空
访问 64 Tag=4 Tag=8 空 空

命中率 = 2/8 = 25\%

[!question] 观察到了什么? 地址 64 在第 6 次访问时被调入,但第 7 次访问 48 时将它替换出,导致第 8 次再次访问 64 时又不命中——这就是典型的**颠簸(Thrashing)**现象。解决方法:提高相联度或增大 Cache 容量。

3.2 平均访存时间

\text{平均访存时间} = \text{命中时间} + \text{不命中率} \times \text{不命中开销}

[!example] 分离 Cache vs 混合 Cache(详解)

已知条件:

  • 程序中 78% 是指令访问(取指),22% 是数据访问(load/store)
  • 不命中开销均为 40 周期

方案一:分离 Cache(指令 16KB + 数据 16KB = 共 32KB)

  • 指令 Cache 不命中率 $mr_I = 1%$,数据 Cache 不命中率 mr_D = 5\%
  • 命中时间均为 1 周期(指令和数据各有独立端口,不会冲突)

逐步计算:

  1. 指令部分平均访存时间 = 1 + 0.01 \times 40 = 1.40 周期
  2. 数据部分平均访存时间 = 1 + 0.05 \times 40 = 3.00 周期
  3. 加权平均 = 0.78 \times 1.40 + 0.22 \times 3.00 = 1.092 + 0.660 = \mathbf{1.752} 周期

方案二:混合 Cache(统一 32KB)

  • 整体不命中率 $mr = 1.5%$(因为容量相同,但指令和数据共享,冲突不命中增加)
  • 命中时间:取指仍为 1 周期,但 load/store 需 +1 周期(因为指令和数据共用端口,load/store 需要额外仲裁)

逐步计算:

  1. 取指平均访存时间 = 1 + 0.015 \times 40 = 1.60 周期
  2. 数据平均访存时间 = 2 + 0.015 \times 40 = 2.60 周期(命中时间 2 周期)
  3. 加权平均 = 0.78 \times 1.60 + 0.22 \times 2.60 = 1.248 + 0.572 = \mathbf{1.820} 周期

结论:分离 Cache 更优(1.752 < 1.820),节省约 3.7\% 的平均访存时间。

[!question] 什么时候混合 Cache 反而更好? 当数据和指令的访问模式不均匀时——比如某个阶段全是取指(循环),另一阶段全是数据访问——混合 Cache 能让 32KB 容量被充分利用,而分离 Cache 各 16KB 可能不够用。此时混合 Cache 的不命中率可能显著低于分离方案。

3.3 CPU 时间与 Cache 的关系

CPU 时间 = IC \times (CPI_{exe} + \frac{\text{访存次数}}{指令} \times mr) \times \tau

四、17 种 Cache 优化技术

17 种优化技术围绕 Cache 性能公式中的三个因素展开,分类关系如下:

graph TD
    ROOT["Cache 性能优化"] --> A["降低不命中率 (8种)"]
    ROOT --> B["减少不命中开销 (5种)"]
    ROOT --> C["减少命中时间 (4种)"]

    A --> A1["增大块大小"]
    A --> A2["增大 Cache 容量"]
    A --> A3["提高相联度"]
    A --> A4["伪相联 Cache"]
    A --> A5["硬件预取"]
    A --> A6["编译器控制预取"]
    A --> A7["编译优化"]
    A --> A8["Victim Cache"]

    B --> B1["非阻塞 Cache"]
    B --> B2["写合并"]
    B --> B3["请求字优先"]
    B --> B4["写缓冲"]
    B --> B5["早重启"]

    C --> C1["小容量简单 Cache"]
    C --> C2["虚拟 Cache"]
    C --> C3["流水化 Cache 访问"]
    C --> C4["路预测"]
分类 技术 基本思想 影响
降低不命中率(8种) 增大块大小 利用空间局部性 块过大会增加不命中开销
增大 Cache 容量 利用时间局部性 增加命中时间
提高相联度 减少冲突不命中 增加命中时间
伪相联 Cache 降低冲突不命中 复杂度适中
硬件预取 提前调入数据 增加总线流量
编译器控制预取 编译器插入预取指令 增加指令开销
编译优化 优化访问模式 依赖编译器能力
Victim Cache 小全相联 Cache 保存替换出的块 减少冲突不命中
减少不命中开销(5种) 非阻塞 Cache 不命中时允许后续请求 提高流水线效率
写合并 合并多次写操作 减少写缓冲区占用
请求字优先 优先传输请求的字 减少等待时间
写缓冲 写操作进入缓冲区异步执行 减少写延迟
早重启 不命中时尽早返回请求字 减少等待时间
减少命中时间(4种) 小容量简单 Cache 降低命中时间 适用于 L1 Cache
虚拟 Cache 用虚拟地址直接索引 增加别名问题
流水化 Cache 访问 Cache 访问分段流水化 提高时钟频率
路预测 预测 Cache 行所在路 预测错误时需重取

[!warning] 优化技术的权衡 几乎每种优化技术都存在"收益-代价"权衡。例如增大块大小降低了强制性不命中,但增加了不命中开销;提高相联度降低了冲突不命中,但增加了命中时间。设计时需要根据具体场景权衡。

五、不命中的三种类型

类型 原因 解决方法
强制性不命中(Compulsory) 首次访问某块 增大块大小、预取
容量不命中(Capacity) Cache 容量不足 增大 Cache 容量
冲突不命中(Conflict) 映射冲突 提高相联度、Victim Cache

六、综合计算题

[!example] 综合计算题:Cache 参数计算与性能分析

已知条件:

  • 处理器 32 位地址
  • Cache 容量 32KB
  • 块大小 64B
  • 采用 8 路组相联
  • LRU 替换策略
  • 写回策略
  • 命中时间 1 个时钟周期
  • 不命中开销 100 个时钟周期
  • 不命中率 2%
  • 程序中 30% 为访存指令(load/store)

问题 1:计算 Tag / Index / Offset 位数

  • Cache 行数 = 32\text{KB} / 64\text{B} = 512 行
  • 组数 = 512 / 8 = 64 组
  • Offset 位数 = \log_2(64) = 6 位
  • Index 位数 = \log_2(64) = 6 位
  • Tag 位数 = 32 - 6 - 6 = 20 位
字段 Tag Set Index Block Offset
位数 20 6 6
位置 [31:12] [11:6] [5:0]

问题 2:计算平均访存时间

\text{AMAT} = \text{命中时间} + \text{不命中率} \times \text{不命中开销} = 1 + 0.02 \times 100 = 1 + 2 = \mathbf{3 \text{ 周期}}

问题 3:计算对 CPI 的影响

假设理想 CPI(无 Cache 不命中)为 2.0:

\text{实际 CPI} = \text{CPI}_{exe} + \text{访存指令比例} \times \text{不命中率} \times \text{不命中开销} = 2.0 + 0.30 \times 0.02 \times 100 = 2.0 + 0.6 = \mathbf{2.6}

Cache 不命中使 CPI 增加了 $0.6$,性能下降了 $30%$。

问题 4:如果将 Cache 容量增大到 64KB(不命中率降为 1%),是否值得?

新的 CPI = 2.0 + 0.30 \times 0.01 \times 100 = 2.0 + 0.3 = 2.3

性能提升 = (2.6 - 2.3) / 2.6 = 11.5\%

[!question] 如何权衡? 64KB Cache 的面积是 32KB 的约 2 倍,但性能仅提升 11.5%。若芯片面积紧张,可考虑用其他优化技术(如提高相联度、硬件预取)来替代简单增大容量——这就是17 种优化技术的组合运用。

关联笔记