Skip to content

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! 种排列),约需 O(log2N!)=O(NlogN) 个 LRU 位 + 复杂逻辑。故现代处理器常用 LRU 的廉价近似
  • 其他:FIFO(最近最少替换)、Random(对抗最坏访问模式时有用)。LRU 总体最常见。

写策略

写操作(如 sw)有两种与主存同步的策略:

  • 写直达(write-through):每次写都同时写回主存,保持缓存与主存一致。简单,但慢、浪费带宽(可能写了又很快被覆盖)。
  • 写回(write-back):只在该行被替换出缓存时才写回主存。平时只要还在缓存里就从缓存读写。更快,是常用做法。
    • 需给每行加一个脏位(dirty bit, D):自调入后被写过则置 1。只有替换一个脏行时才需要先写回主存;只读过(D=0)的行直接丢弃即可。

写回例:16 行、块大小 4 的直接映射缓存(D 位 + V 位 + 24 位 tag),写 0x090x48180x4818=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)时要权衡的核心。