Lec 14 缓存实现
Lec 13 建立了缓存概念与直接映射的基本组织。本讲深入实现:用相联(associativity)减少冲突、用替换策略决定换出谁、用写策略处理写操作,并系统区分缺失类型。
Outline
- 冲突缺失与相联度
- 组相联 / 全相联缓存
- 替换策略(LRU)
- 写策略(写直达 / 写回,脏位)
- 缺失三分类
- 综合对比例题
冲突缺失:直接映射的痛点
直接映射缓存“每个地址只能落在唯一一行”太死板。考虑一段循环:指令在字地址 1024、1025、1026,访问的数据在字地址 37、38、39。
- 1024 行的直接映射缓存:指令映射到行 0/1/2,数据映射到行 37/38/39,互不冲突 → 循环稳定后次次命中。
- 但若数据改放在字地址 2048、2049、2050——它们也映射到行 0/1/2,与指令抢同一行!于是每条指令、每次访存都互相覆盖,次次未命中。
这种因“多个地址争抢同一行”导致的未命中叫冲突缺失(conflict miss)。解决办法:给地址更多可去的位置。
组相联与全相联
- 全相联缓存(fully-associative):任意地址可放任意行——彻底消除冲突缺失。但查找时必须并行比较每一行的 tag,需要大量比较器,代价高。
- N 路组相联缓存(N-way set-associative):折中方案,相当于 N 个直接映射缓存并行工作。每个叫一路(way);地址用 index 选中一组(set),该组在每条路里各有一行,于是同一 index 的地址有 N 个可去的位置。
- 大幅减少冲突缺失,又只需 N 个比较器(而非每行一个)。
- 直接映射 = 1 路;全相联 = 只有一组、路数 = 行数。两者是 N 路的两个极端。
地址切分仍是
tag | index | (block offset) | byte offset,只是 index 现在选的是组而非单行。
替换策略
有了多个可放位置,换出时就要选牺牲谁——这就是替换策略(replacement policy)。
- 理想:换出“未来最久才会用到”的——但我们不知道未来。
- LRU(Least Recently Used,最近最少使用):靠局部性论证——最久没用的,近期最不可能再用。实践中效果好、最常用。
- 2 路 LRU 很简单(记哪一路更近用过即可)。
- N 路 LRU 需要记录所有访问的完整顺序(N! 种排列),约需
个 LRU 位 + 复杂逻辑。故现代处理器常用 LRU 的廉价近似。
- 其他:FIFO(最近最少替换)、Random(对抗最坏访问模式时有用)。LRU 总体最常见。
写策略
写操作(如 sw)有两种与主存同步的策略:
- 写直达(write-through):每次写都同时写回主存,保持缓存与主存一致。简单,但慢、浪费带宽(可能写了又很快被覆盖)。
- 写回(write-back):只在该行被替换出缓存时才写回主存。平时只要还在缓存里就从缓存读写。更快,是常用做法。
- 需给每行加一个脏位(dirty bit, D):自调入后被写过则置 1。只有替换一个脏行时才需要先写回主存;只读过(D=0)的行直接丢弃即可。
写回例:16 行、块大小 4 的直接映射缓存(D 位 + V 位 + 24 位 tag),写
0x09到0x4818。0x4818=0b0100_1000_0001_1000→ tag=0x48、index=0x1、block offset 指向块内第 2 个字、byte offset=0。
- 若 index 1 命中(valid=1 且 tag=0x48):把块内第 2 字改成 0x09,置 dirty=1。
- 若未命中(如该行 tag 是 0x280):要替换该行——先查脏位:脏则把旧行(tag 0x280 对应的地址范围)写回主存,再把新行(tag 0x48 对应整块 0x4810–0x481C)整块载入,置 dirty=0,写入新值。
缺失三分类(3C)
| 类型 | 成因 | 缓解手段 |
|---|---|---|
| 强制缺失(compulsory) | 第一次访问某块,缓存里必然没有(冷启动) | 加大块大小(一次多带些) |
| 冲突缺失(conflict) | 多个地址争抢同一组,相联度不够 | 提高相联度 |
| 容量缺失(capacity) | 工作集大于缓存总容量,装不下 | 加大缓存容量 |
综合对比例题:没有“万能配置”
三个缓存均 8 字、块大小 1、LRU:直接映射(DM)、2 路、全相联(FA)。
- 例1:反复访问
0x0, 0x10, 0x4, 0x24。DM 中 0x4 与 0x24 映射到同一行、互相覆盖 → 命中率 50%;2 路与 FA → 100%。 - 例2:循环访问
0x0,0x4,0x8,0xC,0x10,0x14,0x18,0x1C,0x20(9 个,缓存只 8 行)。- DM:仅 0x0 与 0x20 冲突 → 7/9;
- 2 路:0x0、0x20、0x10 争 2 个位置 → 6/9;
- FA:9 个值塞 8 行,每次都踢掉马上要用的 → 0(典型容量缺失)。
- 例3:访问
0x0,0x4,0x8,0xC,0x20,0x24,0x28,0x2C,0x10,三者命中率分别约 1/9、6/9、0。
结论:没有哪种配置永远最好——相联度、块大小、容量、替换策略都是可调的“杠杆”,最优配置取决于具体应用的访问模式。这正是设计缓存(及 Lab)时要权衡的核心。