Cache 的基本工作原理

Cache 是本章唯一真正降低平均访问延迟的部件——其余所有手段提高的都是带宽(见 3.1.2)。它做到这一点的方式是把最近用过的主存内容留在一小块 SRAM 里,赌下一次访问还会落在里面——这个赌注的依据就是 局部性。

这一节要立住三件事:Cache 与主存之间以块为单位交换;一行里除了数据还有一组控制位;以及平均访问时间的两种口径。后面三节都建立在这三件事上。

机制

块是交换单位,行是存放位置

主存被划分成大小相等的块(Block),Cache 被划分成同样大小的行(Line / 槽 Slot)。块大小行大小Cache 与主存之间一次交换一整块,不是一个字。这是在空间局部性上下的注:取回一个字的同时把它的邻居一起取回来。

块大小必须是 2 的幂,因为主存地址要按位段切分。

CPU 的一次访问

flowchart TD
    A["CPU 发出主存地址"] --> B["按映射方式拆地址<br/>标记 | 行号/组号 | 块内地址"]
    B --> C{"该行有效位=1<br/>且标记相符?"}
    C -->|"是:命中"| D["直接从 Cache 取字<br/>用时 Tc"]
    C -->|"否:缺失"| E["从主存读入整块"]
    E --> F{"有空行?"}
    F -->|"有"| G["装入空行"]
    F -->|"没有"| H["按替换算法淘汰一行<br/>脏则先写回"]
    H --> G
    G --> D

    classDef hit fill:#d5f5e3,stroke:#27ae60
    classDef miss fill:#fadbd8,stroke:#c0392b
    class D hit
    class E,H miss

整个过程对程序员透明:程序发出的始终是主存地址,没有任何指令能指定”访问 Cache”。

Cache 行里有什么

疑问点:Cache 行中的"标记项"包括哪几部分

一个 Cache 行由数据块和标记项两部分构成,标记项就是一行里除数据之外的全部控制信息:

位作用什么时候有
标记 Tag记录这一行当前装的是哪个主存块总是有,位数由映射方式决定
有效位 V这一行是不是有效数据总是有(1 位)
脏位 / 修改位 D这一行被写过、与主存不一致只有写回法才有(1 位)
替换控制位LRU 计数 / 年龄位 / 使用位只有需要替换时才有(直接映射不需要)

有效位不能省。 开机时 Cache 内容是随机的,如果没有有效位,一个随机的标记恰好与地址相符就会读出垃圾数据。进程切换或 Cache 清空时,硬件做的事就是把所有有效位清 0,而不是清数据。

两个容量口径要分清(这是计算题最爱设的陷阱): 数据容量 行数 块大小; 总容量 行数 (块大小 标记位 有效位 脏位 替换位)。 题目说”Cache 容量为 32 KB”时通常指数据容量;问”实际需要多少存储位”时才算总容量。

命中率与平均访问时间 AMAT

设命中率 ,缺失率 ,命中时访问 Cache 用时 ,主存访问用时 。其中 是命中次数、 是缺失次数——分母是总访问次数,不是指令条数。

平均访问时间有两种口径,必须从题干判断用哪一种:

口径一(先查再取主存): 口径二(同时访问直接取主存):

判断方法:看题目给的 是”主存访问时间”还是”缺失附加代价(miss penalty)“,以及题干有没有说”Cache 与主存同时开始访问”。 两种口径都合法,答题时把所用口径写出来。这与 3.1.3的讨论是同一件事。

Cache 的效率(加速比):

加速比

三类缺失

类型原因怎么减少
强制性缺失(冷启动)第一次访问该块,Cache 里必然没有增大块大小(预取更多邻居)
冲突缺失多个块争抢同一行/同一组提高相联度
容量缺失工作集超过 Cache 总容量增大容量

全相联 Cache 没有冲突缺失——任何块可以放进任何行,冲突这个概念本身不存在。这是判断”某次缺失属于哪一类”的关键判据。

块大小的权衡

块变大的好处:更好地利用空间局部性,强制性缺失减少。

块变大的代价:

  • 行数减少 → 冲突缺失增多
  • 每次缺失要搬更多数据 → 缺失代价上升
  • 块内可能有大量用不到的数据 → 有效利用率下降

所以命中率随块大小先升后降,存在一个最优值。 这一条”先升后降”是判断题的常客——“块越大命中率越高”是错的。

边界

Cache 对程序员和操作系统都透明,虚拟存储器不透明。 这是两个缓存对最本质的差别,完整对照见 3.6.5。

Cache 缺失不产生中断。 硬件自己从主存取回,CPU 只是多等几十个周期,不发生进程切换。缺页才产生异常。

“Cache 容量越大命中率越高”只在一定范围内成立,超过工作集大小后收益迅速趋于平坦,而成本和访问延迟继续上升。大容量 Cache 本身也更慢,这正是要分 L1/L2/L3 多级的原因。

命中率的分母是访存次数。 一道题给”执行 100 条指令、平均每条访存 1.2 次、缺失率 5%“,缺失次数是 ,不是 ——这个错误在 3.2.4 的综合题里已经出现过一次。

指令 Cache 与数据 Cache 分开(哈佛结构)是为了消除取指与取数在流水线中的结构冒险,见 第 5 章。分开之后两者的命中率要分别统计。

对照速查

量公式
命中率,分母是访存次数
AMAT 口径一
AMAT 口径二
数据容量行数 × 块大小
总容量行数 ×(块大小 + 标记 + 有效位 + 脏位 + 替换位)
判断对错
Cache 与主存以块为单位交换✅
Cache 对程序员透明✅
有效位可以省略❌
脏位任何写策略都需要❌(只有写回法)
块越大命中率越高❌(先升后降)
全相联没有冲突缺失✅
Cache 缺失会产生中断❌

考点

  • 块是交换单位,块大小 = 行大小,且为 2 的幂
  • 标记项 = 标记 + 有效位 + 脏位(写回法)+ 替换控制位
  • 有效位不可省;清空 Cache 就是清有效位
  • 数据容量与总容量是两个口径
  • AMAT 两种口径都合法,从题干判断并写明
  • 命中率的分母是访存次数
  • 三类缺失:强制性 / 冲突 / 容量;全相联无冲突缺失
  • 块大小与命中率是先升后降的关系
  • Cache 缺失由硬件处理,不产生中断

链接