一、主存块的替换算法

需要装入新数据块、但候选范围内没有可用空闲行时,就要选择一个块进行替换。常见算法如下:

算法 中文名称 替换依据
Random 随机替换 随机选择一个块
FIFO 先进先出 淘汰最早进入 Cache 的块
LRU 最近最少使用 淘汰最久没有被访问的块
LFU 最不经常使用 淘汰访问次数最少的块

1. 随机替换(Random)

核心思想:随机选择一个数据块淘汰。

例如,Cache 中已有 A、B、C,现在要装入 D。若随机选中 B,则替换后保存 A、D、C。

  • 优点:实现简单,无需记录访问时间或次数,管理开销小。
  • 缺点:不考虑使用情况,可能淘汰经常使用的数据,效果受随机结果影响。

2. 先进先出(FIFO)

核心思想:最早进入 Cache 的块最先被淘汰。 可以理解为排队:先来的人先离开。

假设 A、B、C 依次进入 Cache,装入 D 时淘汰 A,进入顺序变为 B、C、D。

重点: FIFO 只看进入顺序。即使 A 最近被访问过很多次,只要它最早进入,仍然优先淘汰。

  • 优点:算法简单,可用队列维护进入顺序,管理开销较低。
  • 缺点:不考虑最近使用情况和访问频率,可能淘汰正在频繁使用的块。

3. 最近最少使用(LRU)

核心思想:淘汰最长时间没有被访问的块。 利用时间局部性,优先保留最近使用过的数据。

数据块 距最近一次访问的时间
A 10 秒
B 2 秒
C 30 秒

此时要装入 D,应淘汰 C,因为它最久没有被访问。

重点: 每次访问一个块,都要更新它的最近使用记录;命中时也要更新。

  • 优点:符合时间局部性,通常比 FIFO 更符合实际访问规律。
  • 缺点:需要维护访问顺序,精确实现有额外的数据结构或硬件开销;对循环扫描等访问模式,效果可能不好。

页置换中的补充结论: FIFO 可能出现 Belady 异常,即增加物理页框数量后,缺页次数反而增加;LRU 不存在这种异常。

4. 最不经常使用(LFU)

核心思想:淘汰访问次数最少的块。

数据块 访问次数
A 10 次
B 3 次
C 20 次

此时要装入 D,应淘汰 B。若多个块的访问次数相同,还需规定进一步的选择规则,例如优先淘汰最久未使用的块。

  • 优点:能够保留长期频繁访问的热点数据,适合访问频率较稳定的场景。
  • 缺点:需要记录次数;过去的热点可能因历史次数较高而长期占据 Cache,因此还需考虑计数衰减或重置。

重点: LRU 看“最近一次什么时候用”,LFU 看“累计用了多少次”。

二、LRU 排序计数器

LRU 的一种实现方式是:为每个候选 Cache 行设置一个排序计数器(LRU 替换位),记录该行在最近访问顺序中的位置。下面以组相联 Cache 的一组为例。

1. 计数值的含义与位数

  • 计数值为 0:最近一次被访问的行。
  • 计数值越小:越近被访问。
  • 计数值越大:越久没有被访问。
  • 需要替换时:淘汰计数值最大的有效行。

若每组有 NN 路,最多需要表示 0,1,2,…,N−10,1,2,\ldots,N-1 共 NN 个顺序位置。因此,在这种排序计数器实现中,每行至少需要:

m=⌈log⁡2N⌉ 位\boxed{m=\lceil\log_2 N\rceil\text{ 位}}

其中,NN 为相联路数,mm 为每行计数器的位数,⌈ ⌉\lceil\ \rceil 表示向上取整。

相联方式 每个 Cache 行的计数器位数
2 路组相联 1 位
4 路组相联 2 位
8 路组相联 3 位
16 路组相联 4 位

计数器位数取决于相联路数,并非单纯取决于 Cache 总容量。上述公式针对每行保存顺序位置的实现,不能直接视为所有 LRU 实现的统一存储开销。

2. 命中时的更新规则

设命中行原来的计数值为 rr:

  1. 命中行计数器清零。
  2. 原计数值小于 rr 的其他有效行加 1。
  3. 原计数值大于 rr 的行保持不变。

例如,4 路组相联 Cache 的某组原来依次保存 A、B、C、D,计数值分别为 0、1、2、3。现在访问 C:

Cache 行 数据块 原计数值 更新后
0 A 0 1
1 B 1 2
2 C 2 0
3 D 3 3

C 从第三新的块变成最新的块,原本排在它前面的 A、B 后移一位;D 原本就比 C 更旧,顺序位置不变。

3. 未命中时的更新规则

目标组仍有空闲行时:新装入行计数器置 0,其他有效行全部加 1。

例如,原来只有 A、B,访问 C 后:

Cache 行 装入前(数据块/计数值) 装入后(数据块/计数值)
0 A/0 A/1
1 B/1 B/2
2 空闲/— C/0
3 空闲/— 空闲/—

目标组没有空闲行时:淘汰计数值最大的行,新装入行计数器置 0,其他有效行全部加 1。

例如,原来保存 A、B、C、D,访问 E 后:

Cache 行 替换前(数据块/计数值) 替换后(数据块/计数值)
0 A/0 A/1
1 B/1 B/2
2 C/2 C/3
3 D/3 E/0

D 的计数值最大,因此被 E 替换。

4. 更新规则汇总

访问情况 更新规则
命中 命中行清零;比它原计数值小的有效行加 1;其余不变
未命中,有空闲行 新装入行清零;其他有效行加 1
未命中,无空闲行 淘汰计数值最大的行;新装入行清零;其他有效行加 1

三、Cache 的写策略

CPU 修改某个主存地址的数据时,先判断对应数据块是否在 Cache 中:

情况 含义 对应策略
写命中(Write Hit) 目标数据已在 Cache 中 全写法、回写法
写不命中(Write Miss) 目标数据不在 Cache 中 写分配法、非写分配法

1. 写命中:全写法与回写法

假设 Cache 和主存中的 A 都为 10,CPU 执行 A = 20。

全写法(Write Through,也叫直写法)

更新 Cache 的同时,向主存发出对应的写请求。 不使用写缓冲、且写入完成后,Cache 和主存中的 A 都为 20。

  • 优点:主存随每次写操作得到更新,控制相对简单。
  • 缺点:每次写操作都要更新主存,写入流量较大;可用写缓冲减少等待。

回写法(Write Back)

先只修改 Cache,设置脏位,等脏块被替换时再写回主存。

  1. 将 Cache 中的 A 改为 20。
  2. 主存中的 A 暂时仍为 10。
  3. 将该 Cache 块的脏位(Dirty Bit)置 1。
  4. 替换该块时,若脏位为 1,先将修改后的数据写回主存。
阶段 Cache 中的 A 主存中的 A
修改后、写回前 20 10

写回完成后,主存中的 A 更新为 20。

  • 优点:减少主存写操作,提高执行效率。
  • 缺点:Cache 与主存可能暂时不一致,需要脏位管理,控制较复杂。

重点: 脏位为 1,表示该块已被修改、尚未写回,替换时需要写回;脏位为 0,表示没有待写回的修改,替换时无需写回。

2. 写不命中:写分配与非写分配

假设 Cache 中没有 A,主存中 A 为 10,CPU 执行 A = 20。

写分配法(Write Allocate)

先将 A 所在的主存块调入 Cache,再执行写操作。 主存是否随本次写操作更新,取决于采用全写法还是回写法。

例如,采用“写分配法 + 回写法”时:Cache 中 A 为 20,主存中 A 暂时仍为 10,该块脏位为 1。

  • 优点:调入后方便后续访问或修改该块。
  • 缺点:需要调入整个数据块,增加传输开销。

非写分配法(No Write Allocate)

不将 A 所在的数据块调入 Cache,直接向主存写入。 写入完成后,主存中 A 为 20,Cache 中仍没有 A。

  • 优点:无需调入数据块,减少不必要的 Cache 空间占用。
  • 缺点:后续访问 A 时,仍可能发生 Cache 未命中。

3. 策略汇总与常见组合

策略 适用情况 核心操作
全写法 写命中 更新 Cache,并向主存写入
回写法 写命中 更新 Cache、设置脏位,脏块替换时写回
写分配法 写不命中 先调入 Cache,再执行写操作
非写分配法 写不命中 不调入 Cache,直接向主存写入

重点: “是否随写操作更新主存”和“写不命中时是否调入 Cache”是两个不同的选择,实际使用时组合起来。

常见组合 写命中 写不命中 特点
全写法 + 非写分配法 更新 Cache,并向主存写入 不调入 Cache,直接向主存写入 主存随写操作更新,写入流量较大
回写法 + 写分配法 更新 Cache,设置脏位 调入 Cache 后修改,设置脏位 减少主存写入次数;替换脏块时先写回

四、全写法中的写缓冲

1. 作用与写入过程

CPU 和 Cache 的速度远快于主存。若每次全写操作都必须等待主存写入完成,CPU 就可能产生较大的等待开销。

写缓冲(Write Buffer) 用于暂存待写入主存的地址和数据,使 CPU 在缓冲区有空位时,不必等待主存完成本次写入。

加入写缓冲后,写命中的处理过程为:

  1. CPU 更新 Cache 中的数据。
  2. 将对应地址与新数据送入写缓冲。
  3. 若写缓冲有空位,CPU 可以继续执行后续指令。
  4. 写缓冲中的请求随后写入主存。

例如,执行 A = 20 后,Cache 中 A 已为 20,写缓冲中保存 A 的地址和新值 20,而主存中 A 可能暂时仍为 10。待写入完成,主存中的 A 才变为 20。

重点: 使用写缓冲后,主存可以延迟更新,但仍属于全写法,因为每次写操作都会产生主存写请求;回写法则将修改保留在 Cache 中,主要在脏块被替换时写回。

2. 写缓冲满与读写相关性

写缓冲容量有限,不能保证 CPU 始终无需等待。

例如,缓冲区只能保存 4 条请求。若 CPU 连续发出 5 条写请求,而主存尚未处理前 4 条,则第 5 条请求必须等待缓冲区腾出空间。

缓冲区状态 CPU 的等待情况
未满 通常无需等待本次主存写入完成
已满(Write Buffer Full) 可能暂停,等待空位

若后续读取的地址对应尚未写入主存的请求,还需检查读写相关性,必要时从写缓冲转发最新数据,避免读取旧值。