329 lines
14 KiB
Markdown
329 lines
14 KiB
Markdown
---
|
||
tags:
|
||
- 计算机系统结构
|
||
- 复习
|
||
- Cache
|
||
create time: 2026-06-15 10:00
|
||
---
|
||
|
||
# 存储系统与 Cache
|
||
|
||
## 概述
|
||
|
||
本文档系统讲解存储层次结构、Cache 的工作原理(映像方式、替换策略、写策略)、命中率计算以及 17 种 Cache 优化技术。Cache 是计算机系统结构的核心考点,几乎每年都有大分值的计算和分析题。
|
||
|
||
> [!tip] 考试重点
|
||
> Cache 命中率计算、平均访存时间计算、分离 Cache vs 混合 Cache 的性能对比是必考内容。17 种优化技术的分类和代表性技术需熟记。
|
||
|
||
## 正文
|
||
|
||
### 一、存储层次结构
|
||
|
||
```mermaid
|
||
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 三部分,不同映像方式的拆分规则不同:
|
||
|
||
```mermaid
|
||
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 性能公式中的三个因素展开,分类关系如下:
|
||
|
||
```mermaid
|
||
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 种优化技术的组合运用**。
|
||
|
||
## 关联笔记
|
||
- [[计算机系统结构/复习文档/计算机系统结构基础与定量原理]]
|
||
- [[计算机系统结构/复习文档/总线与I/O系统]]
|
||
- [[计算机系统结构/index|试题册索引]]
|