EE6156 · PART 2 存储与流水线
习题小程序 →
0 / 6 课

交互式学习手册 · COMPUTER ARCHITECTURE PART 2

从存储墙到流水线冒险,
把 CPU 的两大提速手段吃透

这是根据 EE6156 Part 2 讲义(Lesson 7–12)整理的自学手册。前半部分讲存储系统:为什么需要缓存、缓存怎么映射和替换、写操作怎么处理、虚拟内存怎么翻译地址;后半部分讲流水线:DLX 指令集、五级流水线里每一拍发生了什么,以及结构、数据、控制三类冒险怎么让 CPI 离开 1。每课都配了可以动手的实验和自测题。

6 课 9 个交互实验 每课自测问答 讲者 Cheng Tee Hiang · NTU
Quiz 2
10 月 27 日(周二)

晚上 8:45–9:15,上课讲室,10 道选择题。考第 7–9 周(存储层次、缓存映射 I/II、写策略),虚拟内存不考。带笔、计算器、带照片的证件;9:00 后不能进场。老师划重点 →

Final Exam
11 月 20 日(周五)

上午 9:00–11:00,4 道大题:Q1–Q2 Part 1,Q3 缓存,Q4 流水线(虚拟内存、L2 公式不考)。约 50% 是例题/Tutorial 同类题,30% 换情境应用,20% 较难。

课堂录音

老师划重点:考什么、不考什么

第 1–9 周

根据第 1–9 周的课堂录音整理。老师在课上说过的考试安排、范围、易错点和"不考"的内容都在这里。和讲义有冲突时,以老师课上说的为准;手册里对应的小节已经按老师的口径改过。

Quiz 2 · 10%
10 月 27 日(周二)晚 8:45–9:15
  • 就在上课的讲室。6:30–8:30 正常上课,休息 15 分钟后开考,约 30 分钟。
  • 10 道选择题,老师说"不难":有概念题,也有算地址位、算有效 CPI、做映射的题。
  • 范围:第 7、8、9 周讲的内容(存储层次与 AMAT/CPI、三种缓存映射、3C 缺失、块大小、写策略)。虚拟内存不考。
  • 带:笔(用钢笔/水笔作答)、计算器、带照片的证件。手机放包里,不能带白纸;试卷背面空白可以打草稿。
  • 9:00 以后到的不能进场,算缺考;进场的人 9:00 之前不能离开。座位号会提前公布,记住自己的座位号。
  • NTULearn 上有 mock-up quiz,老师在删掉虚拟内存的题后重新放出来,一定要做一遍。
Final Exam · 60%
11 月 20 日 9:00–11:00
  • 2 小时,4 道大题:Q1、Q2 是 Part 1,Q3、Q4 是 Part 2(Cheng)。
  • Part 2:Q3 考缓存(映射、AMAT、CPI,范围同 Quiz 2),Q4 考流水线(第 10–12 周)。
  • 难度分布:约 50% 和 Tutorial/例题同类,30% 换情境应用,20% 较难。
  • 手算缓存轨迹题在考试里会短很多(大约 4 次访问),不会像课堂练习那样 9–12 次。
  • MIPS 指令表考试会提供,不用背格式,要会用。
其他成绩
Quiz 1 15% · 作业 10% · 参与 5%
  • Quiz 1(第 7 周)和电路作业(第 7 周周五截止)已经结束。
  • 参与分 = 每周课上扫码签到。
  • 第 13 周是备用/复习课。如果时间不够,老师可能删减流水线的部分内容;删掉的内容会做成录像,不考。

明确不考(老师原话"quiz 和 exam 都不考")

可以跳过
  • 虚拟内存与 TLB(9.7 节、Tutorial 9.7):第 9 周老师说没时间讲,只录视频,quiz 和 final 都不考。手册里的 9.7 节和动画留着,有兴趣再看。
  • 多级缓存(L2)的公式:老师"保证"quiz 和 exam 都不考,只需要知道"加 L2 能降低平均缺失代价"这个概念(9.6 节)。
  • 多核缓存一致性(MESI):超出本课范围,只提了一下。
  • 老师因时间不够跳过、改成录像的内容,都不考。

Part 2(Cheng)反复强调的点

公式只认一套
  • AMAT = HR × HT + MR × MT,其中 MT = HT + MP(先查缓存,没命中再去主存)。讲义上写成 "HT + HP" 是笔误。
  • 另一种写法 AMAT = HT + MR × MP 结果一样。老师说选一个公式用到底,别混着用。
  • 实际 CPI = 理想 CPI + 存储停顿;停顿 = 每条指令访存次数 × 缺失率 × 缺失代价,指令和数据分开算再相加。
  • 每条指令访存次数 = 1(取指)+ load/store 占比。题目说"1.2 次取指"就用 1.2(见练习 T7.6+)。
  • 缺失代价给的是 ns 时,先用时钟频率换成周期数(T8.3:50 ns ÷ 2 ns = 25 周期,CPI = 2 + 0.75 + 0.5 = 3.25)。
地址位与硬件
  • 地址位 = log₂(主存字节数);Offset = log₂(块字节数);Index = log₂(组数);Tag = 其余。
  • 组数 = 行数 ÷ 路数。直接映射:组数 = 行数;全相联:只有 1 组,Index = 0 位。
  • 注意 K/M/G:1 KB = 2¹⁰ = 1024 字节,不是 1000;"256 KB" 和 "256 B" 差了 10 位。块大小给的是"字"时先乘 4 换成字节。
  • 老师喜欢的算法:块号 B = ⌊地址 / 块大小⌋,Index = B mod 组数,Tag = ⌊B / 组数⌋;或者直接把地址写成二进制切开。
  • 比较器:个数 = 每次要查的行数(直接映射 1 个,全相联 = 行数,k 路 = k 个),每个比较器的位数 = Tag 位数,XOR 门数 = 两者相乘。T8.2 两路是 2 × 15 = 30(老师口误说成 32)。
  • 还会考"反推":给 Tag 位数和块数,倒推块大小和容量;Index 算出来是 0 就说明是全相联。
手算缓存轨迹
  • 每一步:先算块(地址 ÷ 块大小取整 → 这一块的起止地址),再看 Index 选组,组内比 Tag。
  • 命中:什么都不换,只把这一行的 TSLR 清零、其他行加一。缺失:有空行先填空行,没空行踢 TSLR 最大(最久没用)的。
  • 第一次访问某个块 = compulsory,即使同时把别人踢出去也算 compulsory(老师答疑时确认,compulsory 优先)。
  • 全相联里的重复缺失 = capacity(全相联没有冲突缺失);直接映射/组相联里的重复缺失 = conflict。
  • Ex 8.1 的答案:8 次缺失、4 次命中、命中率 33.3%(讲义上的 3.3% 是笔误)。
概念题素材
  • 写直达(write-through):写缓存同时写主存,简单、无脏位,但访存流量大。写回(write-back):只写缓存并置脏位,被踢出时才写回主存,性能高,现代缓存多用写回。
  • 写分配(write allocate,先把块搬进缓存再写,常配写回)vs 不写分配(直接写主存)。写缓冲(write buffer)让 CPU 不用等写完。
  • 块太大:缺失率先降后升(块数变少,冲突/容量缺失增加),缺失代价变大;常用 64–128 字节。
  • 加大容量比加相联度效果更明显;相联度高了以后再加收益很小。
  • CPU 变快、内存不变:缺失代价按周期算会变大(时钟翻倍,100 → 200 周期,CPI 5.44 → 8.88),停顿占比越来越高。
  • 直接映射:硬件简单但不灵活;全相联:冲突最少但比较器多;组相联是折中。

老师给出的答案和手册原来不一样的地方(已改)

题目老师课上的答案说明
Exercise 7.25 次访存,50 ms vs 15 ms,3.3 倍每轮只算一次 a[i];原来按 10 次算出 5 倍
Tutorial 7.480 ms vs 22 + 6 = 28 ms,2.86 倍老师用缓存 2 ms、主存 20 ms(学生版题目没给时间)
Tutorial 7.6AMAT 15 ns vs 50 ns → 88 分钟,多 28 分钟MT = 10 + 50 = 60 ns;原来用 100 ns 得 19.6 分钟
T7.6+(新增)停顿 1.2 + 1.2 = 2.4,CPI 3.9老师解答里多出来的一题,已加进习题小程序
Exercise 8.1最后的 241 是 compulsory老师课上改正了讲义;手册一直按这个口径

Part 1 的重点回顾

考试范围与重心
  • Lecture 1–6 全部可能考,有理论题也有计算题;重心是 Lecture 3(CPU 模型与电路)和 Lecture 4(MIPS ISA)。
  • "讲义上有的都可能考",别只看计算。复杂度分析(大 O)不会深考。
  • 老师的原话:理解原理,别背答案;Part 1 搞懂了 30 分钟能做完。
老师重复了四五遍的
  • 传输门(T-gate)的两个控制端必须永远相反:一个接信号,另一个接同一个信号经过非门的结果。不要用两个独立输入。
  • PMOS(栅极带圆圈)负责上拉,NMOS 负责下拉;画 D 触发器、用 T-gate 搭多路选择器是常考题。
  • 扇出(fan-out)超限时用两个非门做缓冲,刷新信号。本课只用上升沿触发。
MIPS 编码/解码
  • 先认类型(R / I / J),再找 rs、rt、rd 的位置;I 型的目的寄存器是 rt。
  • 立即数要符号扩展(逻辑运算是零扩展);分支偏移 × 4,跳转地址 ÷ 4;PC + 4。
  • 二进制转十六进制按 4 位一组;补码用计算器的进制转换功能核对。
模型与性能
  • 图灵机:小端序的 +1 / −1 例子,状态转移表,怎么把 TM 编码成二进制串。
  • RAM 模型:运算前必须先 load 到寄存器;函数调用要压栈保存返回地址。PM 模型:指针只能存储和判等。
  • 加速比(Amdahl 定律)、GB 和 GiB 的区别、磁盘延迟 = 寻道 + 旋转 + 传输 + 控制器/排队。

Part 1 的详细复习在首页的芯片蓝图、Tutorial 精讲、模拟卷和速查表里。

LESSON 07

体系结构基础与存储层次

Quiz 2 范围

Part 2 的第一课回答一个问题:为什么 CPU 越来越快,程序却没有同比例变快?答案是 CPU 大部分时间在等内存。这一课先说明冯·诺依曼结构为什么会有"瓶颈",再引出存储层次和局部性原理,最后给出整个 Part 2 都要用的两组公式:平均访存时间(AMAT)和带存储停顿的实际 CPI。

本课课堂练习 · Exercises7.17.27.37.4

7.1 冯·诺依曼结构与它的瓶颈

冯·诺依曼结构的核心是存储程序(stored-program):指令和数据放在同一个主存里。它由这几部分组成:

  • CPU:控制单元(CU)负责取指和控制指令执行;ALU 负责算术和逻辑运算。
  • 主存:同时存程序和数据,按字节编址(每个地址存 1 个字节)。
  • 系统总线:在 CPU、内存、I/O 之间传数据、地址和控制信号。
  • I/O 设备和辅存(硬盘、SSD,非易失)。

问题出在"共享"上:指令和数据走同一个存储器、同一条通路。CPU 处理数据的速度远快于数据在总线上搬运的速度,于是 CPU 经常停下来等,这就是冯·诺依曼瓶颈。

技术趋势:容量涨得快,DRAM 延迟几乎不动
容量增长速度(延迟)增长
逻辑电路每 1.5 年 ×4每 3 年 ×4
DRAM每 3 年 ×4每 10 年 ×2
磁盘每 3 年 ×4每 10 年 ×2

讲义里的 DRAM 数据:1980 年 64 Kb、250 ns,到 1995 年 64 Mb、120 ns。容量涨了 1000 倍,周期时间只缩短一半。CPU 与 DRAM 之间越拉越大的差距被称为存储墙(memory wall)。

怎么缓解瓶颈(讲义列了五条)

  • 缓存与存储层次:把常用数据放在离 CPU 近的地方。这是本课和接下来两课的主线。
  • 哈佛或混合结构:指令和数据走分开的通路,带宽翻倍。
  • 并行与专用化:多核、GPU、向量机、AI 加速器。
  • 存内/近存计算(PIM):把处理器和 RAM 做在同一芯片上,减少数据搬运。
  • 更快的互连与存储:例如 3D 堆叠的高带宽内存 HBM。

7.2 存储层次与局部性原理

硬件上有个绕不开的事实:大的存储器慢,快的存储器小。存储层次的思路是把几种存储器叠起来,让程序"看起来"拥有一块又大、又快、又便宜的内存。

寄存器最快 · 最小 · 最贵
L1 缓存(SRAM)~0.5–2 ns · 32 KB 级
L2/L3 缓存(SRAM)MB 级
主存(DRAM)~10–20 ns · GB 级
辅存(SSD / 硬盘)SSD ~50–150 μs · TB 级 · 最便宜
  • 任何时刻,数据只在相邻两层之间复制。靠近 CPU 的叫上层(小、快、贵),远的叫下层(大、慢、便宜)。
  • 层与层之间搬运的基本单位叫块(block);块放进缓存后占据的位置叫缓存行(cache line)。本课程里 block 和 line 混用。

层次结构之所以有效,靠的是局部性原理:程序在任一时刻只访问地址空间里很小的一部分("90/10 规则":10% 的代码占 90% 的执行时间)。局部性分两种:

类型含义例子缓存怎么利用它
时间局部性刚访问过的数据很快会再被访问循环变量、查找表、循环体里的指令访问过的数据留在缓存里;满了就淘汰最久没用的
空间局部性访问某地址后,邻近地址很快会被访问顺序遍历数组:100, 101, 102, 103缺失时一次取回一整块连续地址
一眼识别两种局部性

序列 100, 101, 102, 103, 100, 101, 102, 200, 200 同时有两种局部性:100→103 依次递增是空间局部性;100 又出现、200 连续两次是时间局部性。Tutorial 9.1(d) 就是这样考的。

主存和缓存到底是什么、在哪里

以一台常见的游戏本为例(Intel Core Ultra 7 处理器、16 GB 内存、2 TB SSD):

层次是什么物理位置这台电脑上
寄存器CPU 正在算的那几个数CPU 每个核内部每个核几十个
缓存(cache)SRAM,很小很快CPU 芯片里面,紧挨着各个核L1 每核 32–48 KB;L2 几 MB;L3 30 MB,所有核共用
主存(main memory)DRAM,平时说的"内存"、RAMCPU 外面:主板上的内存条或内存芯片,经内存总线连到 CPU16 GB
辅存SSD / 硬盘主板上的 M.2 插槽等2 TB SSD
物理位置示意
┌──────────── CPU 芯片(一小块硅片) ────────────┐
│  核 0: 寄存器 + L1    核 1: 寄存器 + L1    …    │
│  L2(每个核或每组核一个)                        │
│  L3(所有核共用)              ← 这些都是"缓存"  │
└───────────────────┬────────────────────────────┘
                    │ 内存总线(芯片外面的导线)
          ┌─────────┴─────────┐
          │   内存条 DRAM     │   ← 这就是"主存"
          └─────────┬─────────┘
                    │ PCIe / NVMe
          ┌─────────┴─────────┐
          │   SSD / 硬盘      │   ← 辅存,关机后数据还在
          └───────────────────┘
  • 主存放"原件":正在运行的程序和它的全部数据。打开程序时,系统先把它从 SSD 读进主存;断电就清空。
  • 缓存放"常用的复印件":只是主存里最近用过的一小部分的副本(上例 L3 只有 30 MB,约为主存的 1/500)。CPU 读某个地址时先查 L1,没有再查 L2、L3,都没有才去主存;从主存取回时把整块复制进缓存,下次再用就快了。
  • 速度差得很远:L1 约 1 ns(几个周期);主存约 50–100 ns(几百个周期);SSD 约 50–150 μs,比主存再慢上千倍。
打个比方

寄存器 = 手里正拿着的那张纸;缓存 = 书桌,只放得下几本书但伸手就拿到;主存 = 房间里的书架,书多得多但要起身去拿;SSD = 楼下的图书馆,什么都有,跑一趟最慢。命中就是书在桌上;缺失就是要去书架拿,多花的时间是缺失代价。缓存里是副本,在副本上改了值,原件就旧了,这就是 9.4 节写策略要解决的问题。

注意:日常说的"手机内存 256 GB"其实是存储空间,相当于这里的辅存;课程里的主存(内存)专指 RAM。考试题默认只有"一级缓存 + 主存"两层,不用管 L2、L3。

补充:显存(GPU 内存)在哪一层

显存在层次图上和主存是同一层:它本身就是 DRAM,只是不属于 CPU 这座金字塔,而是 GPU 自己那座金字塔里"主存"的位置。

层次CPU 这一侧GPU 这一侧
最快、最小寄存器寄存器(每个 SM 有几万个)
片上缓存L1 / L2 / L3(SRAM)共享内存(shared memory)、L1 / L2(SRAM)
片外 DRAM主存:DDR4 / DDR5显存:GDDR6 / GDDR7,数据中心卡用 HBM
更下面SSD / 硬盘经 PCIe 访问 CPU 的主存,再往下是硬盘
  • 技术上它就是 DRAM。它和主存一样处在"片外、大、比 SRAM 慢"那一层,区别是为带宽设计:数据位宽更宽、频率更高。例如一块 8 GB GDDR7 的笔记本显卡,带宽有几百 GB/s;CPU 的双通道 DDR5 约 80–100 GB/s。单次访问的延迟并不比主存短,GPU 靠几千个线程同时访存把延迟藏起来。
  • 两座金字塔之间隔着 PCIe。CPU 不能像用自己的主存那样透明地用显存,数据要显式拷过去(例如 cudaMemcpy)。PCIe 只有几十 GB/s,远低于显存带宽。这条通路就是 7.1 节冯·诺依曼瓶颈在 CPU 和 GPU 之间的翻版,所以跑深度学习时要尽量让数据一直留在显存里。
  • 集成显卡没有独立显存。核显(以及苹果 M 系列的统一内存)的"显存"就是从主存里划出来的一块,和 CPU 共用同一层 DRAM。
  • 和讲义的联系。7.1 节"怎么缓解瓶颈"里的"GPU 等并行加速器"和"3D 堆叠的 HBM"指的就是这一套:HBM 是贴在 GPU 旁边堆叠起来的显存。
考试提示

课程默认只有"缓存 + 主存"两级,显存不在考试范围里,算题时不要把它算进去。

7.3 命中、缺失与 AMAT(课程约定)

本课程默认只有两级:缓存和主存。访问缓存找到了叫命中(hit),没找到就要去主存取块,叫缺失(miss),CPU 为此多等的周期叫存储停顿(memory stall)。几个时间量要分清:

命中时间 Hit time = 缓存访问时间 C 缺失代价 Miss penalty = 主存访问时间 M (本课程默认假设) 缺失时间 Miss time = C + M (先查缓存没找到,再去主存) AMAT = 命中率 × 命中时间 + 缺失率 × 缺失时间

讲义例子:缓存 1 周期,主存 50 周期,命中率 80%。命中时间 1,缺失代价 50,缺失时间 51,缺失率 0.2,所以 AMAT = 0.8×1 + 0.2×51 = 11 周期。

易错:缺失代价 ≠ 缺失时间

缺失代价(penalty)只是"多出来"的那部分时间;缺失时间(miss time)= 查缓存的时间 + 缺失代价。题目说"缓存比主存快 10 倍"时,按课程约定一次缺失要花 C + M = 1 + 10 = 11 个单位,而不是 10。Tutorial 7.1 用这个约定算出加速比正好是 5:不用缓存每次 10,用缓存 AMAT = 0.9×1 + 0.1×11 = 2。

加速比(speedup)是什么

加速比衡量一项改进让同一件事快了多少倍:

加速比 = T旧 / T新 = 性能新 / 性能旧
  • 大于 1 表示变快了:加速比 4 就是快了 4 倍,时间变成原来的 1/4;小于 1 表示反而变慢了。
  • 前后必须是同一个任务(同一个程序、同一批数据),否则比较没有意义。
Part 2 里的几种写法,本质都是"旧时间 / 新时间"
场景公式课程里的例子
加缓存无缓存每次访问时间 / AMATT7.1:10 → AMAT 2,加速比 5
流水线(指令很多)单周期时间 / 流水线时钟周期10.4 节:950 ps / 250 ps = 3.8
流水线(n 个任务)顺序总时间 / 流水总时间10.3 节洗衣房:360 / 210 ≈ 1.71
时钟相同,只比 CPICPI旧 / CPI新12.1 节:5 / 1.25 = 4
时钟也变了(最通用)(CPI旧 × T旧) / (CPI新 × T新)T11.8:(4.6 × 0.8) / (1.2 × 1.0) ≈ 3.07

最后一行是通式:每条指令的平均时间 = CPI × 时钟周期,CPU 时间之比就是这两项乘积之比,前面几行都是它的特殊情况。

加速比的常见坑
  • 分子分母别放反:慢的放上面、快的放下面,结果才大于 1。
  • "快 3 倍"和"快 300%"不是一回事:加速比 3 是时间变成 1/3;"快了 35%"对应加速比 1.35。T7.3 问"慢多少",2.7 / 2.0 = 1.35 倍,即多花 35% 的时间。
  • 流水线加速比有上限:各级完全平衡、指令无限多、没有停顿时,最大加速比 = 级数;不均衡、填充排空、冒险停顿都会让它更小。
  • 只看 AMAT 会出错:T9.2 里 2 路组相联的 AMAT 更小,但时钟周期变长,算 CPU 时间之比反而是直接映射更快。比较要比"整个程序的总时间"。
  • Part 1 的 Amdahl 定律也是加速比:只有比例 f 的部分被加速 s 倍时,整体加速比 = 1 / ((1 − f) + f / s)。T7.1 若按"缺失只花主存时间"的口径算,就是这个公式,得 5.26;按课程口径是 5。

7.4 存储停顿与实际 CPI

理想 CPI 是"所有取指和取数都命中缓存、没有流水线冒险、没有分支代价、不等 I/O"时每条指令平均需要的周期数,代表处理器理论上最好的表现。现实中缺失会带来停顿:

CPU 时间 = (理想周期数 + 存储停顿周期数) × 时钟周期 实际 CPI = 理想 CPI + 每条指令的存储停顿周期 每条指令的存储停顿 = 每条指令的平均缺失代价

先分清:取指 vs 取数

取指(instruction fetch):把指令本身从存储器读出来。CPU 按 PC 里的地址读出那 32 位指令编码,放进指令寄存器 IR,然后 PC 加 4。取数(data access):指令执行时,去存储器里读或写它要处理的数据。

取指取数
读的是什么指令本身(操作码、寄存器号、立即数)程序的数据(数组元素、变量)
地址从哪来PC指令算出来的有效地址,如 R2 + 8
哪些指令有每一条都有,课程默认恰好 1 次只有 load / store(DLX 是 load-store 架构)
读还是写只读load 读,store 写
流水线哪一级IF(第 1 级)MEM(第 4 级)
走哪个缓存指令缓存 I-cache数据缓存 D-cache

例:LW R1, 8(R2) 存在地址 1000,R2 = 500。取指:读 Mem[1000],得到这条指令的编码;取数:读 Mem[500 + 8] = Mem[508],把值放进 R1。这一条指令访问了两次存储器。ADD R1, R2, R3 只有取指(操作数都在寄存器里);SW R3, 8(R2) 是取指加一次写数据。

  • 访存次数 = 1(取指)+ load/store 比例(取数)。
  • 缺失率要分别乘:指令停顿 = 1 × I-缺失率 × 代价;数据停顿 = load/store 比例 × D-缺失率 × 代价。
  • 结构冒险:指令和数据共用一个单端口存储器时,一条指令在 IF 取指、另一条在 MEM 取数,同一周期抢同一个存储器(Lesson 12)。分开的指令/数据缓存(哈佛结构的思路)可以解决。

"load/store 占 40%"是什么意思

这叫指令构成(instruction mix):程序运行时执行的所有指令里,有 40% 是 load 或 store(两者合起来算,比如 25% load + 15% store),其余 60% 是算术逻辑、分支这类不访问数据存储器的指令。

设程序执行 1000 条指令:400 条 LW/SW,600 条 ADD、SUB、BEQZ…… 取指:每条都取一次 → 1000 次;取数:只有 400 条 load/store → 400 次 合计 1400 次 → 平均每条指令 1 + 0.4 = 1.4 次访存 数据停顿 = 0.4 × D-缺失率 × 缺失代价 (例:0.4 × 0.05 × 30 = 0.6 周期/条)
要注意
  • 数的是执行次数,不是代码行数:循环里的一条 LW 跑 100 遍就算 100 条,题目给的比例都是运行时的统计。
  • 40% 只用在数据访问上:取指那一项每条指令都有,前面乘 1,不要再乘 40%。
  • 给比例时先换成百分比:Exercise 7.1 给的是 4 : 3 : 1,load/store 占 3 / 8 = 37.5%。

情形 1:取指和取数的缺失率、缺失代价相同

每条指令的访存次数 = 取指次数 I + 取数次数 D = 1 + load/store 比例 每条指令停顿 = 每条指令访存次数 × 缺失率 × 缺失代价

课程默认每条指令都要取指一次(I = 1),只有 load/store 才访问数据存储器。讲义例子:理想 CPI 2.0,load/store 占 40%,缺失率都是 2%,代价 25 周期 → 访存次数 1.4,停顿 1.4×0.02×25 = 0.7,实际 CPI 2.7,比全命中慢 2.7/2.0 = 1.35 倍。如果根本没有缓存,每次访存都要 25 周期:停顿 1.4×25 = 35,CPI = 37。

情形 2:指令缓存和数据缓存的参数不同

每条指令停顿 = 1 × I-缺失率 × I-缺失代价        + load/store 比例 × D-缺失率 × D-缺失代价

讲义例子:理想 CPI 1.1,指令构成 50% 算术、30% load/store、20% 控制。数据缺失率 10%,代价 50。只算数据缺失:0.3×0.1×50 = 1.5,CPI = 2.6,有 1.5/2.6 = 58% 的时间在等内存。再加上 1% 的指令缺失(0.01×50 = 0.5),CPI = 3.1,停顿占 2.0/3.1 = 64.5%。

解题模板
  1. 写出每条指令的取指次数(1)和取数次数(load/store 比例)。
  2. 分别算指令停顿和数据停顿,相加得每条指令停顿。
  3. 实际 CPI = 理想 CPI + 停顿;停顿占比 = 停顿 / 实际 CPI;变慢倍数 = 实际 CPI / 理想 CPI。
  4. 如果代价以纳秒给出,先换成周期:代价(周期) = 主存时间 ÷ 时钟周期(500 MHz → 2 ns)。
课堂练习 · In-class exercises
Exercise 7.1指令构成比与实际 CPI · Instruction mix and actual CPI
EN

A processor has an ideal CPI of 1.2. The instruction mix ratio is 4 : 3 : 1 for arithmetic/logic, load/store and control instructions. The instruction-cache miss rate is 2%, the data-cache miss rate is 15%, and the miss penalty for both is 40 cycles. Compute (1) the total average miss penalty per instruction, (2) the actual CPI, (3) the percentage of execution time spent on memory stalls.

中

理想 CPI 1.2,算术逻辑 : load/store : 控制 = 4 : 3 : 1。指令缓存缺失率 2%,数据缓存缺失率 15%,两者缺失代价都是 40 周期。求 (1) 每条指令的平均缺失代价总和;(2) 实际 CPI;(3) 存储停顿占执行时间的百分比。

解答 · Solution
EN
Load/store fraction = 3 / (4 + 3 + 1) = 37.5%I-cache stalls = 1 × 0.02 × 40 = 0.8D-cache stalls = 0.375 × 0.15 × 40 = 2.25(1) Total miss penalty = 0.8 + 2.25 = 3.05 cycles/instruction(2) Actual CPI = 1.2 + 3.05 = 4.25(3) Stall share = 3.05 / 4.25 = 71.8%

Convert the ratio into a fraction first. With a 15% data miss rate, data stalls dominate.

中
load/store 比例 = 3 / (4 + 3 + 1) = 37.5%指令停顿 = 1 × 0.02 × 40 = 0.8数据停顿 = 0.375 × 0.15 × 40 = 2.25(1) 总缺失代价 = 0.8 + 2.25 = 3.05 周期/条(2) 实际 CPI = 1.2 + 3.05 = 4.25(3) 停顿占比 = 3.05 / 4.25 = 71.8%

比例要先换成百分比。数据缺失率高达 15%,所以数据停顿占了大头。

Exercise 7.3整段程序的停顿周期 · Total stall cycles for a program
EN

Ideal CPI = 1.5, instruction count IC = 2 × 106, 30% of instructions are loads/stores, the miss rate for both instructions and data is 4%, and the miss penalty is 20 cycles. Each instruction needs one instruction fetch and only loads/stores access data memory. Compute (1) memory references per instruction, (2) memory stall cycles per instruction, (3) total memory stall cycles, (4) actual CPI, (5) total CPU execution cycles.

中

理想 CPI 1.5,指令数 IC = 2 × 106,load/store 占 30%,指令和数据缺失率都是 4%,缺失代价 20 周期。每条指令取指一次,只有 load/store 访问数据存储器。求 (1) 每条指令访存次数;(2) 每条指令停顿;(3) 总停顿周期;(4) 实际 CPI;(5) CPU 总周期数。

解答 · Solution
EN
(1) References/instruction = 1 + 0.3 = 1.3(2) Stalls/instruction = 1.3 × 0.04 × 20 = 1.04(3) Total stalls = 1.04 × 2×10⁶ = 2.08 × 10⁶ cycles(4) Actual CPI = 1.5 + 1.04 = 2.54(5) Total cycles = 2.54 × 2×10⁶ = 5.08 × 10⁶ cycles
中
(1) 访存/条 = 1 + 0.3 = 1.3(2) 停顿/条 = 1.3 × 0.04 × 20 = 1.04(3) 总停顿 = 1.04 × 2×10⁶ = 2.08 × 10⁶ 周期(4) 实际 CPI = 1.5 + 1.04 = 2.54(5) 总周期 = 2.54 × 2×10⁶ = 5.08 × 10⁶ 周期
Exercise 7.4分离的指令/数据缓存 · Separate I-cache and D-cache
EN

Ideal CPI = 1.5. Instruction mix: 50% arithmetic/logic, 30% load/store, 20% branch/control. I-cache miss rate 1% with a 30-cycle penalty; D-cache miss rate 4% (of data accesses) with a 50-cycle penalty. Compute the I-cache stall cycles per instruction, the D-cache stall cycles per instruction, the total memory stall cycles per instruction, the actual CPI and the percentage of execution time spent on memory stalls.

中

理想 CPI 1.5;50% 算术逻辑、30% load/store、20% 分支控制。指令缓存缺失率 1%,代价 30 周期;数据缓存缺失率 4%(按数据访问计),代价 50 周期。求指令停顿、数据停顿、总停顿、实际 CPI 和停顿占比。

解答 · Solution
EN
I-cache stalls = 1 × 0.01 × 30 = 0.3D-cache stalls = 0.3 × 0.04 × 50 = 0.6Total stalls = 0.9 cycles/instructionActual CPI = 1.5 + 0.9 = 2.4Stall share = 0.9 / 2.4 = 37.5%
中
指令停顿 = 1 × 0.01 × 30 = 0.3数据停顿 = 0.3 × 0.04 × 50 = 0.6总停顿 = 0.9实际 CPI = 1.5 + 0.9 = 2.4停顿占比 = 0.9 / 2.4 = 37.5%
交互实验 01
CPI 停顿计算器

改动参数,看实际 CPI 由哪几部分组成。可以点下方的预设,直接套用讲义例题和 Tutorial 的数据。

指令停顿 / 条
–
数据停顿 / 条
–
实际 CPI
–
停顿占比
–
比理想慢
–
理想 CPI指令缺失停顿数据缺失停顿

7.5 循环访存计时题

讲义用一个循环说明缓存为什么有效:循环 10 次访问数组 a[i],缓存访问 1 ms,RAM 访问 10 ms(夸张的数字,只为好算)。约定如下:

  • 每次引用 a[i] 算一次访存;sum、i 都在寄存器里,不产生访存。
  • 整个数组在同一个块里:第一次访问缺失,把块从 RAM 搬进缓存;之后全部命中。
无缓存:10 次 × 10 ms = 100 ms 有缓存:1 次 RAM 搬块 10 ms + 10 次缓存访问 × 1 ms = 20 ms 性能提升 = 100 / 20 = 5 倍
数访存次数:按老师的数法

这类题最容易错在"一共访问了几次 a[i]"。老师课上做 Exercise 7.2 时数的是 5 次(i = 0…4,每轮访问一个元素):无缓存 5 × 10 = 50 ms,有缓存 (1 + 10) + 4 × 1 = 15 ms,提升 3.3 倍。也就是说 if (a[i] > 0) 和后面的 sum ±= a[i] 读的是同一个元素,只算一次。Tutorial 7.4 的 while (sum < 15 && i < 8) 条件只用寄存器,要先模拟到 sum 不再小于 15 时停下,数清实际执行了几轮(4 轮)。

老师的口径:第一次缺失的时间 = 缓存时间 + 主存时间(MT = HT + MP),之后每次只花缓存时间。

课堂练习 · In-class exercise
Exercise 7.2数组循环的访存计时 · Timing an array loop
EN

Consider sum = 0; i = 0; while (i < 5) { if (a[i] > 0) sum += a[i]; else sum -= a[i]; i++; } with a = [4, −2, 7, −5, 3]. Assume each array element a[i] used in an iteration costs one memory access, sum and i are in registers, cache access = 1 ms, main-memory access = 10 ms, and the first array access misses while all later ones hit. Determine (1) the final sum, (2) the number of memory accesses, (3) the total access time without a cache, (4) with a cache, (5) the performance improvement factor.

中

程序:sum = 0; i = 0; while (i < 5) { if (a[i] > 0) sum += a[i]; else sum -= a[i]; i++; },a = [4, −2, 7, −5, 3]。假设每轮用到的数组元素 a[i] 算一次访存,sum、i 在寄存器里;缓存 1 ms,主存 10 ms;第一次访问数组缺失,之后全部命中。求 (1) 最终 sum;(2) 访存次数;(3) 无缓存总时间;(4) 有缓存总时间;(5) 性能提升倍数。

解答 · Solution
EN
(1) sum = 4 + 2 + 7 + 5 + 3 = 21 (negatives are subtracted, i.e. absolute values are added)(2) i = 0…4, one array element per iteration → 5 accesses (the lecturer's count)(3) Without cache = 5 × 10 = 50 ms(4) With cache = first miss (1 + 10) + 4 hits × 1 = 11 + 4 = 15 ms(5) 50 / 15 ≈ 3.3×

The lecturer's in-class answer (week 7) is 5 accesses, 50 ms, 15 ms, 3.3×: one access per array element. Miss time = cache 1 ms + memory 10 ms = 11 ms (MT = HT + MP). Counting the if test and the add/subtract separately (2 per iteration) would give 10 accesses, 100 ms, 20 ms and 5×; in the exam use the lecturer's count and state the assumption.

中
(1) sum = 4 + 2 + 7 + 5 + 3 = 21(负数被减去,相当于加绝对值)(2) i = 0…4,每轮访问一个数组元素 → 5 次(老师课上的数法)(3) 无缓存 = 5 × 10 = 50 ms(4) 有缓存 = 第一次缺失 (1 + 10) + 4 次命中 × 1 = 11 + 4 = 15 ms(5) 50 / 15 ≈ 3.3 倍

老师课上(第 7 周)给的答案就是 5 次、50 ms、15 ms、3.3 倍:每轮按"访问一个数组元素"算一次。缺失时间 = 缓存 1 ms + 主存 10 ms = 11 ms(MT = HT + MP)。如果把 if 判断和加减各算一次(每轮 2 次),会得到 10 次、100 ms、20 ms、5 倍;考试按老师的数法写 5 次,必要时注明假设。

自测 · Self-Check

冯·诺依曼瓶颈的根本原因是什么?为什么哈佛结构能缓解它?
根本原因是指令和数据共享同一个存储器和同一条通路,而 CPU 处理数据的速度远快于通路搬运数据的速度,CPU 只能等。哈佛结构给指令和数据各配一条通路,取指和取数可以同时进行,带宽翻倍,所以能缓解(但不能消除)瓶颈。
"缺失代价"和"缺失时间"差在哪里?缓存 2 周期、主存 80 周期时分别是多少?
缺失代价是缺失时额外付出的时间,课程默认等于主存访问时间,即 80。缺失时间是缺失时总共花的时间 = 命中时间 + 缺失代价 = 82。
为什么题目说"每条指令的访存次数 = 1 + load/store 比例"?
每条指令都必须先从存储器里取出来(取指 1 次,课程默认每条指令恰好 1 次),而只有 load/store 指令才会额外访问数据存储器。所以访存次数 = 1 + load/store 所占比例,例如 40% 的 load/store 对应 1.4 次。
理想 CPI 2.0、停顿 0.7 时,机器比"全部命中"慢多少?停顿占执行时间的多少?
实际 CPI 2.7,慢 2.7/2.0 = 1.35 倍(多花 35% 时间)。停顿占 0.7/2.7 = 25.9%。注意两个比值的分母不同:一个除以理想 CPI,一个除以实际 CPI。
为什么缓存一次搬"一整块"而不是只搬 CPU 要的那个字节?
为了利用空间局部性:CPU 访问一个地址后,很可能马上访问相邻地址。一次搬整块,后面相邻的访问就都能命中。代价是块越大,单次缺失要搬的数据越多,缺失代价越高(Lesson 9 会讲这个权衡)。
LESSON 08

缓存映射 I:三种组织方式

Quiz 2 范围

存储层次的设计目标有两个:用最便宜的技术提供大容量,同时借助局部性让平均访问速度接近最快的那一层。这一课进入缓存内部:一个内存块进了缓存该放在哪一行?怎么判断命中?满了踢谁?讲义用同一个 64 字节内存、4 行缓存的小例子,把全相联、直接映射、组相联三种方式依次走了一遍。本课的手算表格是 Quiz 2 的重点题型。

本课课堂练习 · Exercises8.18.28.3

8.1 性能指标与 AMAT 公式

缩写名称含义
HT命中时间 Hit Time在某一层命中时完成访问的时间。每次访问都先查缓存,所以每次都要付 HT
MP缺失代价 Miss Penalty在命中时间之外,从下一层取数据并交给 CPU 的额外时间
MT缺失时间 Miss Time缺失时总时间,MT = HT + MP
HR / MR命中率 / 缺失率HR + MR = 1

把 MT = HT + MP 代进 Lesson 7 的 AMAT 定义,就得到最常用的形式:

AMAT = HR × HT + MR × MT    = (1 − MR) × HT + MR × (HT + MP)    = HT + MR × MP

两个式子结果相同,Tutorial 8.6 就要求你用两种写法各算一遍来验证。讲义例子:HT = 1,MP = 100,HR = 97%,AMAT = 1 + 0.03×100 = 4 周期。缺失率只有 3%,但缺失代价太大,平均访问时间就从 1 涨到了 4。

讲义给的典型存储器参数(仅作示意)
技术典型用途访问时间每比特成本
SRAM处理器缓存~0.5–2 ns最高
DRAM主存~10–20 ns中等
SSD / 硬盘辅存SSD ~50–150 μs最低

8.2 缓存设计四问与三种映射

  1. 怎么判断命中?比较标签(tag):地址里的标签和缓存行里存的标签相同(且有效位为 1)就是命中。
  2. 新块放在哪?映射方式:全相联、直接映射、组相联。
  3. 候选位置都满了踢谁?替换策略:LRU(最近最少使用)、FIFO、随机。
  4. 块里的哪个字节?用地址最低的偏移位(offset)选。

讲义的统一例子:主存 64 字节(地址 0–63,需要 6 位地址),块大小 4 字节,所以主存有 16 个块;缓存 16 字节,即 4 行。下面点切换,看同一个内存块在三种方式下能去哪:

小演示
内存块 → 缓存行

主存 16 个块(点一个块看它能放哪)
缓存 4 行

8.3 全相联 + LRU:最灵活的放法

全相联(fully associative):任何内存块都可以放进任何一行。地址只分两段:

Tag · 4 位(第 5–2 位)Offset · 2 位

偏移位数 = log₂(块大小) = log₂4 = 2;标签位数 = 地址位数 − 偏移位数 = 6 − 2 = 4(也等于 log₂(主存块数) = log₂16)。每行除了数据,还要存标签和替换用的 LRU 信息。讲义用 TSLR(time since last referenced,距上次访问的次数)记录 LRU:每访问一次,所有有效行的 TSLR 加 1,被访问的行清零;要替换时踢 TSLR 最大的那行。

判断 HIT 还是 MISS:每一步问三个问题

判断命中只看一件事:这个地址所在的"块",此刻是不是在缓存里。和这个地址本身以前有没有被访问过无关。

  1. 它属于哪个块?块大小 4 字节,块从 4 的倍数开始:块起点 = 地址 ÷ 4 取整再 × 4。例如 24 → 24–27,20 → 20–23,04 → 4–7,45 → 44–47。
  2. 这个块现在在缓存里吗?全相联时,4 行里任何一行有它都算。
  3. 在 → HIT,记下它刚被用过。不在 → MISS,把整块搬进来:有空行放空行;4 行都满了,踢掉最久没被用过的那一块(LRU)。

讲义例题逐步走:序列 24, 20, 04, 12, 20, 44, 04, 24, 44

表中括号里记的是这一块最后一次被用到是第几步,要踢的时候踢括号里数字最小的。

步地址 → 块块此刻在缓存里吗?结果访问后缓存里的块
124 → 24–27缓存是空的,不在MISS24–27(1)
220 → 20–23不在MISS24–27(1)、20–23(2)
304 → 4–7不在MISS24–27(1)、20–23(2)、4–7(3)
412 → 12–15不在MISS24–27(1)、20–23(2)、4–7(3)、12–15(4) ← 满了
520 → 20–23在(第 2 步搬进来的)HIT20–23 更新为 (5)
644 → 44–47不在,而且满了MISS踢掉数字最小的 24–27(1),换成 44–47(6)
704 → 4–7在HIT4–7 更新为 (7)
824 → 24–27不在了(第 6 步被踢掉)MISS此时 12–15(4) 最久没用,踢掉它,换成 24–27(8)
944 → 44–47在(第 6 步搬进来的)HIT共 3 次命中、6 次缺失

讲义里的 TSLR 记的是"距上次使用过了几次访问",数字最大的就是最久没用的;上表记"最后使用在第几步"、取最小的。两种记法说的是同一件事,方向相反而已。

注意第 6 步的分类(老师已确认按 compulsory)

讲义把第 6 步(44 第一次被访问,同时引起替换)标成 capacity miss,理由是缓存装不下序列需要的所有块。但按讲义自己第 24 页的定义("块第一次被访问"就是 compulsory),以及教科书的标准 3C 模型,第一次访问一律算 compulsory。下面的模拟器按标准定义分类。考试遇到"首次访问且发生替换"时写 compulsory,可以注明"cache full, LRU block replaced"。老师课上已确认:讲 Ex 8.1 时把最后的 241 改成了 compulsory,第 9 周答疑时又说"第一次访问就是 compulsory,即使同时要踢掉别的块,compulsory 优先"。

最容易想错的两点
  • 以前访问过 ≠ 一定命中。第 8 步的 24 第 1 步就访问过,但第 6 步被踢出去了,所以还是 MISS。
  • 没访问过 ≠ 一定缺失。命中按"块"算。Exercise 8.1 先访问 08,块 8–11 整块进了缓存;之后访问 09,虽然 09 是第一次出现,但它也在 8–11 里,所以是 HIT。这就是空间局部性。

"在不在缓存里"到底怎么看

手算时,看你自己记下的缓存内容。缓存里有什么完全由前面的步骤决定,所以手算时每一步都要把当前缓存里的块记下来(上表最右一栏)。判断第 n 步时,只看第 n − 1 步那一行:要找的块在里面就是 HIT。例如第 8 步访问 24(块 24–27),第 7 步后缓存里是 44–47、20–23、4–7、12–15,没有 24–27,所以 MISS。

硬件上,比较标签(tag)。硬件不存"24–27"这种范围,而是在每一行存一个标签,再加一个有效位(这一行有没有装东西):

  1. 把地址拆开:本例地址 6 位,最低 2 位是偏移;全相联没有索引,高 4 位全是标签,标签其实就是块号。
  2. 拿地址的标签和缓存里存的标签逐个比较。
  3. 某一行标签相同且有效位为 1 → HIT;一个都不同 → MISS。空行有效位是 0,永远不会命中。
第 4 步后缓存里存的标签:0110、0101、0001、0011
步地址二进制(标签 | 偏移)当时缓存里的标签比较结果
5200101 | 000110、0101、0001、0011与第 2 行相同 → HIT
6441011 | 000110、0101、0001、0011都不同 → MISS,踢掉 0110
8240110 | 001011、0101、0001、0011都不同 → MISS(0110 第 6 步已被踢)

这也解释了 09 为什么在 08 之后命中:08 = 0010|00,09 = 0010|01,标签都是 0010,只是偏移不同。偏移只决定从块里取哪个字节,不影响命中判断。

三种组织方式各自要比较哪些行
组织和哪些行比较标签比较器
全相联所有行(4 行同时比)4 个,硬件最贵
直接映射先用索引位直接选出唯一一行,只和这一行比。例如 04 = 00|01|00 只看第 01 行,它存的标签 01(块 20–23)≠ 00 → MISS1 个
组相联先用索引选出一组,再和组内几行比k 个(k 路)

缓存模拟器每一格里显示的就是存的标签("tag 0101"),下方表格的"二进制拆分"一栏给出地址的标签,对照着看就能看清每一步在比什么。

课堂练习 · In-class exercise
Exercise 8.1全相联 + LRU 手算 · Fully associative cache with LRU
EN

Fully associative cache: main memory 256 bytes, 4 cache lines, 4-byte blocks, LRU replacement, initially empty. Reference sequence: 08, 124, 77, 45, 09, 126, 44, 211, 08, 212, 77, 241. (1) Number of bits for the address, offset and tag. (2) For each reference, update the cache and show the tag and time since last referenced. (3) Hit or miss; for a miss, compulsory or capacity. (4) Total hits, misses and hit rate.

中

全相联缓存:主存 256 字节,4 行,块 4 字节,LRU 替换,初始为空。访问序列:08, 124, 77, 45, 09, 126, 44, 211, 08, 212, 77, 241。(1) 地址、偏移、标签各几位;(2) 每次访问后的缓存内容(标签与距上次访问次数 TSLR);(3) 命中还是缺失,缺失是强制还是容量;(4) 总命中、总缺失和命中率。

解答 · Solution
#Addr 地址Tag Index OffsetBlock 块Set/Line 组/行TagResult 结果Evicted 被替换Cache after 访问后缓存 (TSLR)
18000010 008–11–000010MISS compulsory8–11(0)
2124011111 00124–127–011111MISS compulsory8–11(1), 124–127(0)
377010011 0176–79–010011MISS compulsory8–11(2), 124–127(1), 76–79(0)
445001011 0144–47–001011MISS compulsory8–11(3), 124–127(2), 76–79(1), 44–47(0)
59000010 018–11–000010HIT8–11(0), 124–127(3), 76–79(2), 44–47(1)
6126011111 10124–127–011111HIT8–11(1), 124–127(0), 76–79(3), 44–47(2)
744001011 0044–47–001011HIT8–11(2), 124–127(1), 76–79(4), 44–47(0)
8211110100 11208–211–110100MISS compulsory76–798–11(3), 124–127(2), 208–211(0), 44–47(1)
98000010 008–11–000010HIT8–11(0), 124–127(3), 208–211(1), 44–47(2)
10212110101 00212–215–110101MISS compulsory124–1278–11(1), 212–215(0), 208–211(2), 44–47(3)
1177010011 0176–79–010011MISS capacity44–478–11(2), 212–215(1), 208–211(3), 76–79(0)
12241111100 01240–243–111100MISS compulsory208–2118–11(3), 212–215(2), 240–243(0), 76–79(1)
EN

(1) Address = log₂256 = 8 bits, offset = log₂4 = 2 bits, no index in a fully associative cache, tag = 8 − 2 = 6 bits.

(4) 4 hits (09, 126, 44, 08) and 8 misses (7 compulsory, 1 capacity: the second access to 77). Hit rate = 4/12 = 33.3%. 09 shares block 8–11 with 08, 126 shares block 124–127 with 124, and 44 shares block 44–47 with 45: these hits come from spatial locality.

中

(1) 地址 log₂256 = 8 位,偏移 log₂4 = 2 位,全相联没有索引,标签 = 8 − 2 = 6 位。

(4) 命中 4 次(09、126、44、08),缺失 8 次(强制 7、容量 1:最后一次访问 77),命中率 4/12 = 33.3%。09 与 08 同在块 8–11,126 与 124 同在块 124–127,44 与 45 同在块 44–47,这些命中来自空间局部性。

8.4 直接映射:每个块只有一个家

全相联每次都要比较所有行,大缓存里还要维护 LRU,硬件代价高。直接映射(direct-mapped)让每个内存块只能放在固定的一行:用地址中间几位做索引(index)直接选行。

Tag · 2 位Index · 2 位Offset · 2 位
偏移位 = log₂(块大小) = 2 索引位 = log₂(缓存行数) = log₂4 = 2 标签位 = 地址位 − 索引位 − 偏移位 = 6 − 2 − 2 = 2 行号 = 块号 mod 行数 (块号 = 地址 ÷ 块大小,取整)

查找三步:用 index 选中一行 → 比较这一行存的 tag 和地址的 tag → 相同则命中,从行里按 offset 取字节;不同则缺失,从主存取块覆盖这一行。

同一个序列在直接映射下:24(行 10)、20(行 01)、04(行 01,踢掉 20)、12(行 11)、20(行 01,踢掉 04)、44(行 11,踢掉 12)、04(行 01,踢掉 20)、24 命中、44 命中,只有 2 次命中。20 和 04 都映射到行 01,互相把对方踢出去,这就是冲突缺失。

直接映射不需要替换策略

每个块只有一个位置可去,发生冲突时那一行原来的块直接被替换,不用 LRU、FIFO。这也是它比全相联便宜、快的原因之一。

讲义第 43 页的两个小问题

第 3 步表格里 24 那一行的标签写成了 00,应为 01(24 = 01 10 00₂)。另外第 3 步把 04 的缺失标成 conflict,但 04 是第一次被访问,同一页第 6 步的 44 情况完全相同却标成 compulsory。按定义两者都是 compulsory。

直接映射和全相联到底差在哪

两者看起来像:都要存标签、比标签,讲义例题结果也差不多(全相联 3 次命中,直接映射 2 次)。根本区别只有一个:一个块能放在哪里。

直接映射全相联
块能放哪只能放一个固定的行:行号 = 块号 mod 行数任何一行都行
地址拆分Tag | Index | OffsetTag | Offset(没有 Index)
查找时比较几次用 Index 直接选出那一行,只比 1 次和所有行都比一遍
满了踢谁不用选,那一行原来的块直接被替换要选,按 LRU 踢最久没用的
冲突缺失有:别的行空着也不能用没有
硬件1 个比较器,便宜、快每行一个比较器 + LRU 记录,贵、慢

打个比方:直接映射像按学号尾号分配的储物柜,你的东西只能放 3 号柜,3 号柜被人占了就得把他的拿走,哪怕旁边柜子全空着;全相联像随便用哪个空柜子,空间利用最好,但找东西时要把所有柜子都看一遍。

一个差别很大的例子:主存 64 字节、块 4 字节、缓存 4 行,访问 0, 16, 0, 16, 0, 16。地址 0 是块 0,地址 16 是块 4,直接映射下行号都是 0 mod 4 = 4 mod 4 = 0,两块抢同一行。

访问直接映射(都只能去第 0 行)全相联(随便放)
0MISS 第 0 行 = 块 0MISS 放第 1 行
16MISS 踢掉块 0,第 0 行 = 块 4MISS 放第 2 行,块 0 还在
0MISS 踢掉块 4HIT
16MISS 踢掉块 0HIT
0MISSHIT
16MISSHIT
合计0 次命中4 次命中

直接映射另外 3 行从头到尾空着,两个块却在第 0 行来回互踢(冲突缺失,见 8.6 节)。两者真正结果一样的情况:访问的块刚好落在不同的行、彼此不冲突时;以及缓存只有 1 行时。两者都是组相联的特例:直接映射是每组 1 行(1 路),全相联是所有行一组。可以在下面的缓存模拟器里亲手试:主存 64、行数 4、块大小 4,序列填 0, 16, 0, 16, 0, 16,切换"直接映射"和"全相联"各运行一次对比。

课堂练习 · In-class exercise
Exercise 8.2直接映射手算 · Direct-mapped cache
EN

Same memory and sequence as Exercise 8.1, but a direct-mapped cache with 4 lines and 4-byte blocks (no replacement policy needed). (1) Number of bits for the address, offset, index and tag. (2) Update the cache for each reference. (3) Hit or miss; compulsory or conflict. (4) Total hits, misses and hit rate.

Sequence: 08, 124, 77, 45, 09, 126, 44, 211, 08, 212, 77, 241

中

主存和序列同 Exercise 8.1,改为直接映射缓存,4 行,块 4 字节(不需要替换策略)。(1) 地址、偏移、索引、标签各几位;(2) 每次访问后的缓存内容;(3) 命中还是缺失,缺失是强制还是冲突;(4) 总命中、总缺失和命中率。

序列:08, 124, 77, 45, 09, 126, 44, 211, 08, 212, 77, 241

解答 · Solution
#Addr 地址Tag Index OffsetBlock 块Set/Line 组/行TagResult 结果Evicted 被替换Cache after 访问后缓存
180000 10 008–1120000MISS compulsoryL2: 8–11
21240111 11 00124–12730111MISS compulsoryL2: 8–11
L3: 124–127
3770100 11 0176–7930100MISS compulsory124–127L2: 8–11
L3: 76–79
4450010 11 0144–4730010MISS compulsory76–79L2: 8–11
L3: 44–47
590000 10 018–1120000HITL2: 8–11
L3: 44–47
61260111 11 10124–12730111MISS conflict44–47L2: 8–11
L3: 124–127
7440010 11 0044–4730010MISS conflict124–127L2: 8–11
L3: 44–47
82111101 00 11208–21101101MISS compulsoryL0: 208–211
L2: 8–11
L3: 44–47
980000 10 008–1120000HITL0: 208–211
L2: 8–11
L3: 44–47
102121101 01 00212–21511101MISS compulsoryL0: 208–211
L1: 212–215
L2: 8–11
L3: 44–47
11770100 11 0176–7930100MISS conflict *44–47L0: 208–211
L1: 212–215
L2: 8–11
L3: 76–79
122411111 00 01240–24301111MISS compulsory208–211L0: 240–243
L1: 212–215
L2: 8–11
L3: 76–79
EN

(1) Address 8 bits, offset 2 bits, index log₂4 = 2 bits, tag 8 − 2 − 2 = 4 bits. Line = block number mod 4.

(4) 2 hits (09, 08) and 10 misses (7 compulsory, 3 conflict). Hit rate = 2/12 = 16.7%. Blocks 31 (124–127), 19 (76–79) and 11 (44–47) all map to line 3 and keep evicting each other. The last access to 77 (marked *) would also miss in a fully associative cache of the same size (it is a capacity miss in Ex 8.1), so strict 3C calls it capacity; this exercise only asks compulsory vs conflict, so it is recorded as conflict.

中

(1) 地址 8 位,偏移 2 位,索引 log₂4 = 2 位,标签 8 − 2 − 2 = 4 位。行号 = 块号 mod 4。

(4) 命中 2 次(09、08),缺失 10 次(强制 7、冲突 3),命中率 2/12 = 16.7%。块 31(124–127)、19(76–79)、11(44–47)全都映射到行 3,反复互相踢出。标 * 的最后一次 77:同样大小的全相联缓存也会缺失(Ex 8.1 里它是容量缺失),严格按 3C 应记 capacity;本题只要求区分强制与冲突,记 conflict。

8.5 组相联:两者的折中

k 路组相联(k-way set-associative)把缓存行分成若干组,每组 k 行。索引选中一组,块可以放在组内任意一行,组内满了按 LRU 替换。

组数 = 行数 ÷ k  索引位 = log₂(组数) 组号 = 块号 mod 组数 k = 1 → 直接映射;k = 行数 → 全相联

讲义例题:4 行的 2 路组相联

把 4 行缓存两两分成一组:组 0 两行、组 1 两行。规则只有两条:每个块只能进一个固定的组(像直接映射);进组之后组里两行随便放,组满了踢组里最久没用的那块(像全相联)。所以说它是两者的折中。

地址拆分(6 位地址,从高位到低位)
字段位数怎么来的作用
Tag3 位(第 5–3 位)6 − 1 − 2认出是哪个块
Index1 位(第 2 位)log₂(组数 2)去哪个组
Offset2 位(第 1–0 位)log₂(块大小 4)块里第几个字节

换个算法也一样:块号 = 地址 ÷ 4,组号 = 块号 mod 2,标签 = 块号 ÷ 2。所以有个好用的结论:块号是偶数进组 0,是奇数进组 1。Index 恰好就是块号的最低位。

序列里每个地址去哪个组
地址Tag | Index | Offset块块号组
24011 | 0 | 0024–276(偶)组 0
20010 | 1 | 0020–235(奇)组 1
04000 | 1 | 004–71(奇)组 1
12001 | 1 | 0012–153(奇)组 1
44101 | 1 | 0044–4711(奇)组 1

20、04、12、44 四个块全挤进组 1,而组 1 只有 2 行;只有 24 进组 0。逐步走时,每一步先算组号,只在那个组里找。括号里是"最后一次被用到是第几步",要踢时踢数字小的:

步地址 → 块去哪组那组里有它吗?结果访问后组 0访问后组 1
124 → 24–27组 0空MISS24–27(1)—
220 → 20–23组 1空MISS24–27(1)20–23(2)
304 → 4–7组 1没有,还有一行空MISS24–27(1)20–23(2)、4–7(3)
412 → 12–15组 1没有,组 1 满了MISS,踢 20–2324–27(1)12–15(4)、4–7(3)
520 → 20–23组 1没有(第 4 步被踢)MISS,踢 4–724–27(1)12–15(4)、20–23(5)
644 → 44–47组 1没有MISS,踢 12–1524–27(1)44–47(6)、20–23(5)
704 → 4–7组 1没有(第 5 步被踢)MISS,踢 20–2324–27(1)44–47(6)、4–7(7)
824 → 24–27组 0有HIT24–27(8)不变
944 → 44–47组 1有HIT不变44–47(9)、4–7(7)

结果 2 次命中、7 次缺失:5 次是第一次访问(强制缺失),第 5、7 步是冲突缺失。讲义第 64 页把第 4 步(12 第一次被访问)标成 conflict,按定义应算 compulsory,口径问题同 8.3 节。

为什么这么惨:组 0 一直空着一行

组 0 从头到尾只用了一行,另一行一直空着;组 1 那边四个块抢两个座位,不停互相踢。它们不能去组 0 坐,因为组号由地址决定,没得选。这正是冲突缺失:缓存其实还有空位,只是映射规则不让用(8.6 节详细讲)。同一序列下全相联命中 3 次(第 7 步的 04 还在缓存里),2 路和直接映射都只有 2 次。这个序列恰好不利于 2 路;一般情况下提高相联度能减少冲突缺失。

硬件上怎么判断命中:用 Index 位选出一组(例如 20 的 Index = 1,选组 1);把地址的 Tag 和组内两行存的 Tag 同时比较,需要 2 个比较器(直接映射 1 个,全相联 4 个);有一行相同且有效位为 1 就命中。缺失时每组用 1 个 LRU 位记住哪一路最近用过,决定踢谁。

一般来说,每组放几块?

要分清两件事:每组最多同时装几块是硬件定死的;主存里哪些块会被分到这一组由地址决定。

  • 每组最多同时装 k 块,固定不变。k 路组相联每组有 k 行。本例 4 行 2 路:组数 = 4 ÷ 2 = 2,组 0、组 1 各 2 行,各自最多同时装 2 块。组与组之间不能互相借位。
  • 主存的块平均分给各组,而且交替排列。组号 = 块号 mod 组数。主存 64 字节共 16 块,偶数块归组 0,奇数块归组 1,每组"负责" 8 个块,但同一时刻只有 2 个座位。
  • 某次运行里各组实际装了几块,看访问序列。本例 1 块去组 0、4 块去组 1,所以组 1 挤得不停踢;如果顺序访问 0, 4, 8, 12, 16…,块号奇偶交替,两组会用得差不多平均。
本例:每组负责哪些主存块
组归它的块号对应地址
组 00, 2, 4, 6, 8, 10, 12, 14(偶数)0–3、8–11、16–19、24–27、32–35、40–43、48–51、56–59
组 11, 3, 5, 7, 9, 11, 13, 15(奇数)4–7、12–15、20–23、28–31、36–39、44–47、52–55、60–63
换成别的参数,同样的算法
参数组数 = 行数 ÷ k每组行数 = k每组负责的主存块数
讲义:64 B 主存,4 行,2 路,4 B 块2216 ÷ 2 = 8
Tutorial 8.8:256 B 主存,8 行,2 路,4 B 块4264 ÷ 4 = 16
Exercise 8.3:256 B 主存,4 行,2 路,4 B 块2264 ÷ 2 = 32

Tutorial 8.8 有 4 组,组号是块号除以 4 的余数,不再是看奇偶。那题的块号 5、9、13、17 除以 4 都余 1,全进了组 1,和本例是同一种"挤在一组"的情况。

三种组织方式对比(同为 4 行缓存)
方式组数 × 每组行数地址划分块能去哪
全相联1 × 4Tag 4 | Offset 2任意一行
2 路组相联2 × 2Tag 3 | Index 1 | Offset 2指定组里的任意一行
直接映射4 × 1Tag 2 | Index 2 | Offset 2唯一一行
课堂练习 · In-class exercise
Exercise 8.32 路组相联手算 · 2-way set-associative cache
EN

Same memory and sequence again, with a 2-way set-associative cache of 4 lines, 4-byte blocks and LRU replacement. (1) Bits for the address, offset, index and tag. (2) Update the cache (tag and time since last referenced). (3) Hit or miss; compulsory or conflict. (4) Total hits, misses and hit rate.

Sequence: 08, 124, 77, 45, 09, 126, 44, 211, 08, 212, 77, 241

中

主存和序列不变,改为 2 路组相联,4 行,块 4 字节,LRU。(1) 地址、偏移、索引、标签各几位;(2) 每次访问后的缓存内容(标签与 TSLR);(3) 命中还是缺失,强制还是冲突;(4) 总命中、总缺失和命中率。

序列:08, 124, 77, 45, 09, 126, 44, 211, 08, 212, 77, 241

解答 · Solution
#Addr 地址Tag Index OffsetBlock 块Set/Line 组/行TagResult 结果Evicted 被替换Cache after 访问后缓存 (TSLR)
1800001 0 008–11000001MISS compulsoryS0: 8–11(0)
212401111 1 00124–127101111MISS compulsoryS0: 8–11(1)
S1: 124–127(0)
37701001 1 0176–79101001MISS compulsoryS0: 8–11(2)
S1: 124–127(1), 76–79(0)
44500101 1 0144–47100101MISS compulsory124–127S0: 8–11(3)
S1: 44–47(0), 76–79(1)
5900001 0 018–11000001HITS0: 8–11(0)
S1: 44–47(1), 76–79(2)
612601111 1 10124–127101111MISS conflict76–79S0: 8–11(1)
S1: 44–47(2), 124–127(0)
74400101 1 0044–47100101HITS0: 8–11(2)
S1: 44–47(0), 124–127(1)
821111010 0 11208–211011010MISS compulsoryS0: 8–11(3), 208–211(0)
S1: 44–47(1), 124–127(2)
9800001 0 008–11000001HITS0: 8–11(0), 208–211(1)
S1: 44–47(2), 124–127(3)
1021211010 1 00212–215111010MISS compulsory124–127S0: 8–11(1), 208–211(2)
S1: 44–47(3), 212–215(0)
117701001 1 0176–79101001MISS conflict *44–47S0: 8–11(2), 208–211(3)
S1: 76–79(0), 212–215(1)
1224111110 0 01240–243011110MISS compulsory208–211S0: 8–11(3), 240–243(0)
S1: 76–79(1), 212–215(2)
EN

(1) 4 lines ÷ 2 ways = 2 sets: address 8 bits, offset 2 bits, index 1 bit, tag 8 − 1 − 2 = 5 bits. Set = block number mod 2.

(4) 3 hits (09, 44, 08) and 9 misses (7 compulsory, 2 conflict). Hit rate = 3/12 = 25%. Comparing the three organizations: fully associative 33.3% > 2-way 25% > direct-mapped 16.7%; higher associativity removes conflict misses.

中

(1) 4 行 ÷ 2 路 = 2 组:地址 8 位,偏移 2 位,索引 1 位,标签 8 − 1 − 2 = 5 位。组号 = 块号 mod 2。

(4) 命中 3 次(09、44、08),缺失 9 次(强制 7、冲突 2),命中率 3/12 = 25%。三种组织对比:全相联 33.3% > 2 路 25% > 直接映射 16.7%,相联度越高,冲突缺失越少。

8.6 3C 缺失分类

类型原因典型场景怎么减少
强制缺失
Compulsory / Cold
块第一次被访问,从没进过缓存程序刚开始运行预取;增大块(效果有限)
冲突缺失
Conflict
多个块争同一组/同一行,缓存其实还有空位直接映射里 A、B 反复互踢提高相联度(2 路、4 路)
容量缺失
Capacity
工作集比缓存大,全相联也装不下1 MB 缓存反复访问 10 MB 数据增大缓存;加一级缓存

冲突缺失到底是什么意思

缓存其实装得下,但映射规则规定这个块只能去某一行(或某一组),偏偏那里被别的块占了,只好把它踢出去;后来再访问被踢掉的块,就缺失了。一句话:不是缓存太小,是座位分配规则太死。

打个比方

停车场 4 个车位,规定单号车只能停 A 区(2 个位),双号车只能停 B 区(2 个位)。连续来了 4 辆单号车:A 区两个位被反复争夺,后来的把先停的赶走;B 区一直空着一个位,但单号车不准停过去。被赶走的车再回来又找不到位,这就是冲突。如果改成"随便停"(全相联),4 辆车都停得下,就不会互相赶。

8.5 节的 2 路组相联例子里,第 5、7 步就是冲突缺失:第 4 步 12 进组 1 时把 20–23 踢掉,第 5 步再访问 20 就 MISS;第 5 步又把 4–7 踢掉,第 7 步访问 04 也 MISS。这两次缺失时组 0 都还空着一行,总容量是够的。

判断方法,按顺序问两步:

  1. 这个块是第一次被访问吗?是 → 强制缺失,不管它有没有引起替换。
  2. 不是第一次的话,换成同样行数的全相联缓存(也用 LRU)跑同一个序列,这一步会命中吗?会命中 → 冲突缺失(全相联装得下,是映射规则害的);也缺失 → 容量缺失(缓存本身太小,怎么放都装不下)。

验证:全相联在第 4 步后装着 24–27、20–23、4–7、12–15,第 5 步访问 20 会命中,所以 2 路这里的缺失是冲突缺失。反过来,全相联自己第 8 步访问 24 缺失,它没有映射限制,只能怪 4 行装不下,所以是容量缺失。标准 3C 模型里每次缺失只归入一类。冲突缺失只出现在直接映射和组相联里,全相联没有冲突缺失;这也是提高相联度能降低缺失率的原因。

答题口径

讲义的直接映射/组相联练习(Exercise 8.2、8.3,Tutorial 8.8)只让你在 compulsory 和 conflict 之间选:凡是"以前访问过、后来被同一行/同一组的别的块踢掉"的缺失,都写 conflict。缓存模拟器里标 * 的少数几次,严格按上面第 2 步算是容量缺失,答题时按题目给的两个选项写即可。

冲突缺失 vs 容量缺失
问题冲突缺失容量缺失
换成全相联有用吗?有用没用
加大缓存有用吗?有时有用有用
根本原因映射碰撞缓存太小

8.7 比较器的硬件代价

判断命中靠等值比较器:把存储的标签和地址标签逐位异或(XOR),全部为 0 且有效位为 1 才算命中。一个 T 位比较器需要 T 个 XOR("位比较")。

组织方式比较器个数每个比较器宽度同时进行的位比较总数
直接映射1TT × 1
k 路组相联kTT × k
全相联(C 行)CTT × C

讲义例子:4 位标签、8 行。直接映射 1 个比较器、4 次位比较;全相联 8 个比较器、32 次位比较。注意同一个缓存换组织方式时 T 也会变(全相联没有索引位,标签最长),Tutorial 8.7 则直接给定了 T = 6。

设计权衡
  • 直接映射:硬件简单、面积小、访问快(索引直接定位,数据可以和标签比较并行读出);但冲突缺失多。
  • 全相联:没有冲突缺失,命中率通常最高;但比较器多、功耗大、面积大,访问时间可能更长。
  • 同样的芯片面积:全相联可能命中率更高,直接映射可以做得更大。组相联是两者的折中。
交互实验 02
缓存模拟器(LRU · 3C 分类 · TSLR)

选好组织方式和参数,输入访问序列,然后一步一步执行。每一步都会显示地址怎么拆分、落在哪一组、是否命中、属于哪类缺失,以及缓存当前的内容。预设里有讲义例题、Exercise 8.1–8.3 和 Tutorial 8.8 / 9.1 / 9.6。

0 / 0
TagIndex(组号)Offset
#地址二进制拆分块组Tag结果替换

分类规则:第一次访问的块记 compulsory;全相联里的其余缺失记 capacity;直接映射/组相联里的其余缺失记 conflict。标 * 的表示同样大小的全相联 LRU 缓存也会缺失,按严格 3C 应记 capacity(讲义的直接映射/组相联练习只要求区分 compulsory 和 conflict)。TSLR 每访问一次全体有效行加 1,被访问的行清零。

自测 · Self-Check

主存 256 字节、块 4 字节、缓存 8 行。全相联、直接映射、2 路组相联的地址分别怎么划分?
地址 log₂256 = 8 位,偏移 2 位。全相联:Tag 6 | Offset 2。直接映射:8 行 → 索引 3 位,Tag 3 | Index 3 | Offset 2。2 路:4 组 → 索引 2 位,Tag 4 | Index 2 | Offset 2。这正是 Tutorial 8.8(i)。
直接映射里,地址 37 应该放进哪一行?标签是多少?(主存 256 B、块 4 B、8 行)
块号 = 37 ÷ 4 = 9(取整),行号 = 9 mod 8 = 1,标签 = 9 ÷ 8 = 1。用二进制看:37 = 001 001 01₂,Tag 001、Index 001、Offset 01,一致。
为什么直接映射不需要 LRU?
每个内存块只能放在唯一的一行,缺失时没有"选哪一行"的问题,原来那一行的块直接被替换。只有当一个块有多个候选位置(组相联、全相联)时才需要替换策略。
A、B 两个块在直接映射缓存里反复互踢。这是什么缺失?换成全相联还会发生吗?
冲突缺失。缓存里其实还有空行,只是映射规则把 A、B 都指向同一行。换成全相联(或提高相联度)后两者可以同时待在缓存里,这类缺失就消失了。容量缺失则不同,换成全相联也没用,只能加大缓存。
6 位标签、16 行缓存。直接映射和全相联各需要多少个比较器、多少次位比较?
直接映射:1 个比较器,6 次位比较。全相联:16 个比较器,16 × 6 = 96 次位比较(Tutorial 8.7)。
LESSON 09

缓存 II:块大小、写策略与虚拟内存

Quiz 2 范围(9.7 不考)

Lesson 8 解决了"块放哪、怎么找"。这一课补上剩下的设计问题:缓存和块该做多大、相联度该取几、写操作怎么保证主存拿到正确的值、缺失代价怎么随 CPU 变快而放大、多级缓存怎么算。最后把同一套思路搬到主存和磁盘之间,得到虚拟内存和 TLB。

本课课堂练习 · Exercises9.1

9.1 减少缺失:容量与相联度

  • 强制缺失:第一次访问,躲不掉;程序跑几百万条指令时,这部分可以忽略。
  • 冲突缺失:办法 1 是加大缓存(每行被更少的地址共享),办法 2 是提高相联度(每组多几行)。
  • 容量缺失:缓存装不下程序用到的所有块(例如缓存 64 KB、程序 64 MB),只能加大缓存。

讲义的统计图给了两条经验:缺失率随缓存变大和相联度变高而下降;4 路组相联的缺失率约等于两倍大的直接映射缓存。从直接映射到 2 路收益最大,在很多情况下缺失率能降 20% 以上;再往 4 路、8 路走,收益越来越小,而硬件复杂度和访问时间却在增加。

9.2 块大小的权衡

大块的好处大块的坏处
更好地利用空间局部性,减少强制缺失缓存容量固定时块数变少,冲突/容量缺失增加(还会削弱时间局部性)
同样容量下需要的标签更少每次缺失要传更多字节,缺失代价变大
块太大时,取回来的很多字节根本用不上,浪费空间和带宽
缺失代价 = 取到第一个字节的延迟 + 其余字节的传输时间 经验规则:块大小 < √(缓存大小);常见选择 64–128 字节 目标:选让 AMAT = HT + MR × MP 最小的块大小

讲义演示:2 组、8 字节块的直接映射缓存,访问 24 时整个 24–31 块进入缓存,随后访问 28 直接命中,尽管 28 从没被访问过。这就是块大小带来的空间局部性收益。Tutorial 9.6 用一个只有一块的缓存把这个效果量化:块 8 B 时缺失率 50%,16 B 时 37.5%,32 B 时 25%。

9.3 地址位划分通用方法

缓存由三个参数决定:组数、相联度(每组块数)、块大小。

缓存容量 = 组数 × 相联度 × 块大小 块数(行数)= 容量 ÷ 块大小 组数 = 块数 ÷ 相联度 Offset = log₂(块大小) Index = log₂(组数) Tag = 地址位 − Index − Offset

块数怎么算:容量 ÷ 块大小

块数就是缓存一共能放多少个块,也就是缓存有多少行。关键是先把两个数换成同一单位(字节),再写成 2 的幂。以 Exercise 9.1(32 KB 缓存、256 B 块)为例:

  1. 把 32 KB 换成字节:1 KB = 1024 = 2¹⁰,32 = 2⁵,所以 32 KB = 2⁵ × 2¹⁰ = 2¹⁵ = 32768 字节。
  2. 块大小写成 2 的幂:256 = 2⁸。
  3. 2 的幂相除就是指数相减:2¹⁵ ÷ 2⁸ = 2⁷ = 128 块。直观地说,32768 字节的数据区切成每份 256 字节,正好 128 份,每份一行、放一个块。

后面几步都从这个 128 出发:Offset = log₂256 = 8(一块 256 字节,编号 0–255 要 8 位);组数 = 128 ÷ 相联度(直接映射 128、4 路 32、全相联 1);Index = 7、5、0;Tag = 32 − Index − 8 = 17、19、24。

记住这几个 2 的幂
写法2 的幂字节数
1 KB2¹⁰1024
32 KB2¹⁵32768
64 KB2¹⁶65536
256 KB2¹⁸262144
1 MB2²⁰1048576
4 GB2³²—

有了这张表,"容量 ÷ 块大小"就是指数相减:64 KB ÷ 64 B = 2¹⁶⁻⁶ = 1024 块(T9.5);256 KB ÷ 64 B = 2¹⁸⁻⁶ = 4096 块(T8.5)。

容易出错
  • KB 是 1024,不是 1000:算成 32000 ÷ 256 就除不尽了。
  • 块大小给的是"字"时先换成字节:T8.2 的"8-word blocks",1 字 = 4 字节,块是 32 B,不是 8 B。
  • 这里算的是缓存里的块,不是主存里的块:主存块数 = 2³² ÷ 2⁸ = 2²⁴。这正好说明全相联的标签为什么是 24 位:没有索引,标签要能区分主存里所有 2²⁴ 个块。

Offset 选块内字节,Index 选组,Tag 确认组里存的是不是要找的块。讲义两个例子(32 位地址、64 KB、32 B 块):

组织块数组数OffsetIndexTag比较器
直接映射2¹¹ = 20482048511161 个 16 位
2 路组相联20482¹⁰ = 1024510172 个 17 位
规律

容量和块大小不变时,相联度每翻一倍,组数减半,Index 少 1 位,Tag 多 1 位;全相联时 Index = 0,Tag 最长。标签总存储 = 块数 × (Tag 位 + 有效位等附加位)。2 路组相联每组还需要 1 个 LRU 位(讲义图里没画)。

反向题

Tutorial 8.4(b)(c) 是反过来给 Tag 求别的量:已知 32 位地址、直接映射 32 块、Tag 21 位 → Index 5 位 → Offset = 32 − 21 − 5 = 6 位 → 块 64 B → 容量 2 KB。已知容量 256 B、Tag 28 位、块 16 B → Offset 4 → Index = 32 − 28 − 4 = 0 → 只有 1 组 → 16 块全在一组,即 16 路(全相联)。

课堂练习 · In-class exercise
Exercise 9.1三种组织的地址位 · Address fields for three organizations
EN

A cache has a capacity of 32 KB and a block size of 256 bytes, with 32-bit addresses. Determine the number of tag, index and block-offset bits for (1) a direct-mapped cache, (2) a four-way set-associative cache, (3) a fully associative cache.

中

缓存容量 32 KB,块 256 字节,32 位地址。分别求 (1) 直接映射;(2) 4 路组相联;(3) 全相联时的标签、索引、块内偏移位数。

解答 · Solution
EN
Blocks = 2¹⁵ / 2⁸ = 128; offset = log₂256 = 8(1) Direct-mapped: 128 sets → index 7, tag 32 − 7 − 8 = 17(2) 4-way: 128 / 4 = 32 sets → index 5, tag 19(3) Fully associative: 1 set → index 0, tag 24

Pattern: with capacity and block size fixed, doubling the associativity removes one index bit and adds one tag bit.

中
块数 = 2¹⁵ / 2⁸ = 128;偏移 = log₂256 = 8(1) 直接映射:128 组 → 索引 7,标签 32 − 7 − 8 = 17(2) 4 路:128 / 4 = 32 组 → 索引 5,标签 19(3) 全相联:1 组 → 索引 0,标签 24

规律:容量和块大小不变时,相联度每翻一倍,索引少 1 位、标签多 1 位。

交互实验 03
地址拆分器 · 缓存 / 虚拟地址

9.4 写策略:主存怎么拿到正确的值

写和读不一样:写不一定要让 CPU 等(可以用写缓冲),但写会修改缓存,于是缓存和主存可能暂时存着同一数据的不同版本;有些 I/O 设备(DMA)还会绕过缓存直接读主存。四种机制:

机制做法特点
写直达
Write-through
每次写同时更新缓存和主存主存永远最新;简单,但内存流量大
写回
Write-back / copy-back
只写缓存,被改过的块打上脏位(dirty bit);块被替换时才写回主存流量小、性能高;需要管理脏位,控制更复杂
写缓冲把要写主存的数据先放进缓冲区,CPU 继续执行常和写直达搭配;缓冲满了 CPU 仍要等
缓存一致性多核下用 MESI 等协议保证各缓存与主存一致超出本课程范围

为什么"写"会带来麻烦

读只是把数据复制一份到缓存,缓存和主存内容一样,不会出问题。写会改值:缓存里有块 100–103 = A B C D,主存也是 A B C D;CPU 把 X 写到 101,缓存那份变成 A X C D,主存那份改不改?不改的话,同一个地址在两处就是两个不同的值。主存的值在这几种时候要紧:

  • 块被踢出缓存时:缓存里的新值若没写回主存,就永远丢了。
  • I/O 设备(DMA)直接读主存时:它绕过缓存,会读到旧值。
  • 多核时:别的核读到的也可能是旧值。

写策略要解决的就是"什么时候、怎么把新值交给主存"。

写命中:写直达 vs 写回,逐步对比

"写命中"指要写的块已经在缓存里。依次执行 Store X → 101、Store Y → 102、Store Z → 101,最后这个块被替换出缓存(Tutorial 9.4):

步骤写直达:缓存写直达:主存写回:缓存写回:主存脏位
初始A B C DA B C DA B C DA B C D0
Store X → 101A X C DA X C DA X C DA B C D1
Store Y → 102A X Y DA X Y DA X Y DA B C D1
Store Z → 101A Z Y DA Z Y DA Z Y DA B C D1
块被替换直接丢掉已是最新脏位为 1,整块写回A Z Y D—
主存一共被写3 次1 次
  • 写直达(write-through):每次写,缓存和主存同时更新。好处是主存永远最新、简单可靠,块被踢时直接丢掉;坏处是每次写都要访问慢速的主存,流量大。上例写了 3 次主存,第一次写的 X 后来还被 Z 覆盖,等于白写。
  • 写回(write-back):写的时候只改缓存,等这个块被踢出缓存时才一次性写回主存。好处是同一块的多次写合并成一次,流量小、速度快(上例 X 根本没写到主存);坏处是主存有一段时间是旧的,控制更复杂,踢块时若要先写回会多花时间。

脏位(dirty bit)是什么

脏位是缓存每一行上额外的 1 个比特,标记"这一行被改过没有"。"脏"的意思是这份副本已经被改动,和主存里的原件不一样了。

脏位意思这一行被踢出时
0(clean)缓存里这份和主存那份一模一样直接丢掉,什么都不用做
1(dirty)这一行在缓存里被写过,主存那份已经是旧的必须先把整块写回主存,再丢掉
每一行存:| 有效位 V | 脏位 D | 标签 Tag | 数据(整个块) |
  • 每一行(每个块)1 位,不是每个字节 1 位。块里哪怕只改了一个字节,整行就算脏,写回时整块一起写。
  • 什么时候变:块从主存搬进缓存时 D = 0;对这一行写(store)时 D = 1;只读不改变 D;被踢出时看 D 决定要不要写回。
  • 只有写回策略才需要脏位:写回在写时不改主存,必须记住哪些行的主存副本已经过期。只读过的块 D 一直是 0,踢出时不用白写一次主存,这也是写回省流量的地方。写直达每次都同时改主存,行永远是"干净"的,不需要脏位。
  • T9.4(c) 问每次操作后的脏位:初始 0,三次写之后都是 1、1、1,直到块被写回、踢出。

写缓冲:让写直达不那么慢

写和读不一样:读时 CPU 必须拿到数据才能继续算;写时 CPU 不需要拿回任何东西。所以可以加一个小队列:CPU 把"地址 + 数据"往缓冲里一放就继续执行,缓冲在后台慢慢写进主存,好比把信投进邮筒就走,不用等邮递员送到。只有缓冲满了(写得太快,主存来不及消化),CPU 才要停下来等。写缓冲通常和写直达搭配。

缓存一致性是多核的问题:每个核有自己的缓存,核 1 改了 x,核 2 缓存里还是旧的 x,需要 MESI(Modified / Exclusive / Shared / Invalid)这类协议来通知和作废旧副本。讲义说超出本课程范围,知道名字即可。

写缺失时怎么办

写缺失:要写的地址所在的块不在缓存里。例如执行 Store X → 205,块 204–207 不在缓存里,有两种处理:

写分配
write allocate / fetch-on-write
不写分配
no-write allocate / write-around
做法先把块 204–207 从主存搬进缓存(和读缺失一样),再在缓存里写 X直接写主存的 205,缓存不动
想法刚写过的地方很可能马上还要读写(局部性),搬进来后面就能命中有些数据写完就不再用(例如初始化一个大数组),搬进缓存反而挤掉有用的块
之后再读 205命中缺失,要去主存取
通常搭配写回:块进了缓存,之后的多次写都只改缓存,最后一起写回写直达:反正每次写都要写主存,没必要再把块搬进缓存
两套常见组合
组合写命中写缺失特点
写回 + 写分配只写缓存,脏位置 1先搬块进缓存,再写缓存性能高,现代缓存主流
写直达 + 不写分配缓存和主存都写只写主存简单,主存永远最新,通常配写缓冲

数主存被写了几次:写直达 = 写的次数;写回 = 被踢出时脏块的个数。

权衡(Tutorial 9.4 e、f 的答案点)

写回的优点:同一块的多次写合并成一次写回,内存流量和带宽占用小;写操作以缓存速度完成,性能高、功耗低。写直达的优点:主存始终是最新的,一致性简单(DMA 读主存不会读到旧值);不需要脏位;替换块时不用先等写回。趋势是现代缓存多用写回。

交互实验 04
写直达 vs 写回(Tutorial 9.4)

缓存里已经有块 100–103 = A B C D,主存同样是 A B C D。依次执行:Store X → 101,Store Y → 102,Store Z → 101,最后这个块被替换出去。对比两种策略下缓存、主存和脏位的变化,以及一共写了几次主存。

缓存行(块 100–103)dirty = 0
主存 100–103主存写 0 次

9.5 缺失代价被 CPU 放大

改进缓存性能有三个方向:降低命中时间、降低缺失率、降低缺失代价。讲义用一个例子说明为什么 CPU 越快,缓存越重要。

CPI_ideal = 2,MP = 100 周期,load/store 36%,I-缺失 2%,D-缺失 4% 指令停顿 = 2% × 100 = 2  数据停顿 = 36% × 4% × 100 = 1.44 CPI = 2 + 3.44 = 5.44,停顿占 3.44 / 5.44 = 63%
理想 CPI 越低,停顿占比越高(停顿固定为 3.44)
CPI_ideal总 CPI停顿占比
25.4463%
14.4477%
0.53.9487%
0.253.6993%

超标量处理器每拍可以完成多条指令,CPI_ideal 可以小于 1。另一种情况是时钟频率翻倍而内存不变:同样 50 ns 的主存访问现在要花两倍的周期数,MP 从 100 变成 200,停顿变成 4 + 2.88 = 6.88,CPI = 8.88。内存延迟本身没变,但 CPU 每次缺失等待的周期数翻倍。

Tutorial 9.2 的陷阱

比较两种缓存时不能只看 AMAT。2 路组相联的缺失率低、AMAT 也更低(2.90 ns vs 2.98 ns),但它让时钟周期变长 10%,而时钟周期影响的是每条指令的全部周期,不只是访存。算 CPU 时间:直接映射 IC × (2.0 × 2 + 1.3 × 0.014 × 70) = 5.274·IC ns;2 路 IC × (2.0 × 2.2 + 1.3 × 0.010 × 70) = 5.31·IC ns,反而是直接映射更快。

9.6 多级缓存 公式不考

老师:L2 公式 quiz 和 exam 都不考

第 9 周老师讲完两级缓存的例子后说,多级缓存的公式只是给大家参考,保证 quiz 和 final 都不考。只要知道"L1 缺失时先去 L2,比直接去主存代价小得多,所以加 L2 能降低停顿"就够了。下面的内容可以快速浏览。

只有一级缓存时,L1 缺失就得直接去主存,一次要等 100 个周期;L1 又不能做大,做大了就变慢。办法是在 L1 和主存之间再加一层更大一点、稍慢一点的 L2(芯片面积够时还有 L3):

CPU → L1(很小、很快)→ L2(大一些、慢一些)→ 主存(很大、很慢)
  • L1 缺失先找 L2:L2 有,只付 L2 的时间(比如 19 或 25 周期),不用去主存;L2 也没有,才去主存。
  • L2 通常是统一缓存:指令和数据共用一个 L2;L1 则分成指令缓存和数据缓存两个。

下面所有公式都从一个办法推出来:数一数一共多少次访问、每次花多少时间,加起来再除以访问次数。

用 1000 次访问数一遍(Tutorial 9.3 的数据)

L1 命中 1 周期、缺失率 5%;L2 命中 19 周期、缺失率 2%;从主存取一块 50 周期。CPU 一共发出 N = 1000 次访问:

走到哪一层次数每次多花合计
所有访问都先查 L1100011000
L1 缺失 → 去 L21000 × 5% = 5019950
L2 也缺失 → 去主存50 × 2% = 15050
总时间2000 周期

平均每次访问 = 2000 ÷ 1000 = 2 周期,这就是 AMAT。把这张表写成公式:

AMAT = 1 + 5% × 19 + 5% × 2% × 50 = HTL1 + MRL1 × HTL2 + MRL1 × MRL2 × MPL2 AMAT = HTL1 + MRL1 × (HTL2 + MRL2 × MPL2) MPL1 = HTL2 + MRL2 × MPL2 = 19 + 2% × 50 = 20 (= L2 自己的 AMAT) AMATL1 = HTL1 + MRL1 × MPL1 = 1 + 5% × 20 = 2

括号里正好是 L2 自己的 AMAT:L1 一旦缺失,平均还要再花 20 个周期才能拿到数据。所以"L1 的缺失代价就是 L2 的平均访问时间"。公式和一级缓存完全一样,只是 L1 的缺失代价不再是主存时间。这就是 Tutorial 9.3(a)(b):AMATL2 = 20,AMATL1 = 2。

局部缺失率 vs 全局缺失率

还是那 1000 次访问:L1 缺失 50 次,L2 缺失 1 次。

怎么算L1L2
局部缺失率本级缺失次数 ÷ 到达本级的访问次数50 / 1000 = 5%1 / 50 = 2%
全局缺失率本级缺失次数 ÷ CPU 发出的总访问次数50 / 1000 = 5%1 / 1000 = 0.1%
  • 对 L1 两者一样,因为所有访问都先到 L1;对 L2 差别很大,L2 只看得到 L1 漏下来的那 50 次。
  • 全局 = 局部1 × 局部2 = 5% × 2% = 0.1%(讲义写作 n1/N × n2/n1 = n2/N):CPU 的所有访问里,最终有多少要跑到主存。
  • L2 的局部缺失率常常看起来不低,因为它接到的都是 L1 搞不定的"难"访问,不代表 L2 没用。衡量 L2 的效果要看全局缺失率。

讲义的 CPI 例子,逐项拆开

接着 9.5 节:理想 CPI 2,load/store 36%,L1 指令缓存缺失率 2%、数据缓存 4%。L1 缺失后去 L2 要 25 周期;加了 L2 之后,只有 0.5% 的访问最终去主存(全局缺失率),去主存 100 周期。每次访存的平均停顿分两部分:

每次访存停顿 = MRL1 × 去 L2 的时间 + 全局缺失率 × 去主存的时间
来源去 L2 的停顿去主存的停顿
取指(每条 1 次)1 × 2% × 25 = 0.51 × 0.5% × 100 = 0.5
取数(每条 0.36 次)0.36 × 4% × 25 = 0.360.36 × 0.5% × 100 = 0.18
小计0.860.68
CPI = 2 + 0.86 + 0.68 = 3.54 没有 L2:停顿 = 2% × 100 + 0.36 × 4% × 100 = 2 + 1.44 = 3.44,CPI = 5.44

省在哪里:原来每次 L1 缺失都要等 100 周期;现在大部分 L1 缺失在 L2 就解决了,只付 25 周期,只有 0.5% 的访问还要付 100 周期。

先分清给的是哪种缺失率
题目给的怎么用
L2 的全局缺失率(讲义这个例子:0.5%)直接乘每条指令的访存次数和主存时间
L2 的局部缺失率(Tutorial 9.3:2%)先乘 L1 缺失率,或用嵌套公式从 L2 往上逐级算 AMAT

Tutorial 9.3(c):理想 CPI 2.0,25% 是 load/store,题目只讲数据缓存。每次数据访问的停顿 = MRL1 × MPL1 = 5% × 20 = 1 周期(也就是 AMATL1 − HTL1 = 2 − 1,命中时间已经算在理想 CPI 里了);每条指令停顿 = 0.25 × 1 = 0.25,CPI = 2.25。

典型参数(讲义)
L1L2
总容量16–64 KB500–8000 KB
块大小32–64 B32–128 B
缺失代价10–25 周期100–1000 周期
缺失率2%–5%0.1%–2%(全局)

读法:越往下越大(L2 比 L1 大十几到上百倍)、越慢、块越大;L1 的缺失代价是去 L2 的时间,L2 的缺失代价是去主存的时间;L2 一栏写的是全局缺失率,所以数字很小。

9.7 虚拟内存与 TLB 不考

老师:虚拟内存不讲、不考

第 9 周老师说时间不够,虚拟内存只录一个视频放到 NTULearn,quiz 和 final 都不考,mock quiz 里的虚拟内存题也会删掉。这一节和地址翻译动画留着,有兴趣再看;复习时间紧可以直接跳到 Lesson 10。

先抓住一句话:程序用的地址是"假的"(虚拟地址),每次访存前都要翻译成"真的"(物理地址)。TLB 是为了让这个翻译变快而加的小缓存。

为什么需要虚拟内存

电脑里同时跑着几十个程序,共用一块物理内存。如果每个程序直接用物理地址:程序得知道自己被放在内存哪里,两个程序都想用地址 1000 就打架;程序比内存大就没法运行;一个程序写错地址,可能改坏别的程序甚至操作系统。

虚拟内存是一层间接映射:每个程序都以为自己独占一整片从 0 开始的地址空间(32 位就是 4 GB),程序里写的都是虚拟地址,由硬件和操作系统偷偷翻译成物理地址。讲义列的三个问题就这样解决了:

问题怎么解决
程序比物理内存大暂时不用的部分放在磁盘上,用到时再调进内存,造成"内存无限大"的假象。主存相当于磁盘的缓存,靠的仍是局部性
空闲内存碎片化每个程序各有一套翻译表,程序的各块可以分散放在主存的不同位置,零散的空闲内存也能用上
安全与共享程序只能访问自己翻译表里有的地址,看不到别人的私有数据;需要共享时让两张表指向同一块物理内存

分页:按"页"来翻译

一个字节一个字节地翻译太费,所以把内存切成固定大小的页(page),常见是 4 KB。翻译只管"哪一页",页内第几个字节原样保留:

虚拟地址 = | 虚拟页号 VPN | 页内偏移 offset |         ↓ 查页表 物理地址 = | 物理页号 PPN | 页内偏移 offset | ← 偏移原样抄下来 偏移位 = log₂(页大小) VPN 位 = 虚拟地址位 − 偏移位 PPN 位 = 物理地址位 − 偏移位

Tutorial 9.7:32 位虚拟地址、30 位物理地址、4 KB 页 → 偏移 log₂4096 = 12 位,VPN = 32 − 12 = 20 位,PPN = 30 − 12 = 18 位。

为什么要有 VPN 和 PPN 两个编号

一句话:VPN 是"程序以为的页号",PPN 是"这一页在内存条上实际的位置",页表负责把前者换成后者。

VPN(虚拟页号)PPN(物理页号)
是谁的编号程序自己给自己的页编的号:0、1、2……连续排下去物理内存里每个页框的编号
从哪来程序发出的虚拟地址的高位查页表(或 TLB)得到
用来做什么当作"查询键":去页表找第 VPN 项(在 TLB 里当标签比较)和偏移拼成物理地址,真正拿去访问缓存和内存
每个程序都一样吗每个程序都有自己的 VPN 0、1、2……全机器只有一套,所有程序共用

为什么不能只用一个编号?因为"程序以为的位置"和"实际放的位置"本来就不一样,而且必须允许它们不一样:

程序它的 VPN实际放在 PPN
程序 A07
程序 A12
程序 A2不在内存(在磁盘,有效位 0)
程序 B05
程序 B13
  1. 程序不用管自己被放在哪:A、B 都从第 0 页开始写代码,操作系统放到哪都行,A 的连续几页在内存里可以东一块西一块(7、2)。
  2. 同样的 VPN 互不干扰:A 的第 0 页和 B 的第 0 页翻译到不同的 PPN(7 和 5),彼此看不到对方,这就是隔离和保护。
  3. 不是每一页都要在内存里:A 的第 2 页可以暂时在磁盘上,用到时才调进来。

如果程序直接用物理页号,这三点都做不到。打个比方:订酒店时确认单写"你的第 1 间房"(VPN),每个客人都从 1 开始编;前台的分配表(页表)写着"张三的第 1 间 = 512 号房",512 是真实房号(PPN);房间里的床位号就像页内偏移,不需要翻译。

虚拟地址位数、物理地址位数和页的关系

关键只有一点:虚拟页和物理页一样大,所以两边的偏移位数相同,虚拟地址和物理地址的位数差全部落在页号上。

由什么决定Tutorial 9.7含义
虚拟地址位数CPU 给程序用多宽的地址32 位每个程序"以为"自己有 2³² = 4 GB 地址空间
物理地址位数机器实际能装多少内存30 位真正的物理内存最多 2³⁰ = 1 GB
页大小操作系统和硬件约定4 KB虚拟页、物理页都是 4 KB
虚拟地址 32 位 = | VPN 20 位 | 偏移 12 位 | 物理地址 30 位 = | PPN 18 位 | 偏移 12 位 | 偏移相同,多出的 2 位全在页号上 虚拟页 2²⁰ ≈ 100 万页(4 GB ÷ 4 KB) 物理页 2¹⁸ ≈ 26 万页(1 GB ÷ 4 KB)

物理页只有虚拟页的 1/4:一个程序的 4 GB 空间如果全用满,最多只有 1/4 能同时待在内存里,其余只能放在磁盘上,用到时再调进来。这就是有些页表项有效位为 0、会发生页缺失的根本原因:虚拟空间比物理内存大,装不下。这也说明了页表为什么在主存里:每个程序的页表有 2²⁰ 项,按每项 4 字节算就要 4 MB,芯片里放不下;每次都去主存查又太慢,所以要有 TLB。

做题步骤:① 偏移 = log₂(页大小),两边相同;② VPN = 虚拟地址位 − 偏移,PPN = 物理地址位 − 偏移;③ 翻译时把 VPN 换成 PPN、偏移照抄,结果自然是物理地址的位数。

页表与地址翻译

页表(page table)存放在主存里,每个程序一张,每一项记录一个虚拟页。真实的页表项还有脏位、引用位(用来近似 LRU)和访问权限(只读、可写等)。Tutorial 9.7 的页表:

VPNPPN有效位说明
0x123450x1A2341这一页在内存里,物理页号 0x1A234
0x123460x0B5671在内存里
0xABCDE0x1F8901在内存里
0x22222—0这一页不在内存里(在磁盘上或还没分配)

翻译 0x12345678:

  1. 4 KB 页的偏移是 12 位,正好是最后 3 个十六进制位:偏移 = 678,VPN = 12345。
  2. 查页表:VPN 0x12345 → PPN 0x1A234,有效位 1。
  3. 拼起来:物理地址 = 0x1A234 拼接 678 = 0x1A234678。

同理 0x12346ABC → 0x0B567ABC,0xABCDEFFF → 0x1F890FFF。偏移为什么不变:虚拟页和物理页大小一样、都按页对齐,翻译只是把"第几页"换掉,字节在页里的位置不变。

页表里的有效位是什么

有效位回答一个问题:这一个虚拟页现在在不在物理内存里,也就是这一行写的 PPN 能不能用。

有效位意思CPU 访问这一页时
1这一页在内存里,PPN 那一栏就是它所在的物理页正常翻译:PPN 拼接偏移得到物理地址
0这一页不在内存里:被换到磁盘上了,或者还没分配PPN 没有意义、不能用 → 触发页缺失,交给操作系统
  • 为什么需要它:PPN 字段里总会有某个二进制数,没有有效位的话,硬件分不清它是真的物理页号还是没用的旧值。有效位是给硬件看的开关:只有它是 1,才相信这一行的 PPN。
  • 什么时候变:操作系统把一页从磁盘调进内存时,填好 PPN、有效位置 1;内存不够、把某一页换出到磁盘时,有效位置 0(这一页在磁盘的位置由操作系统另外记录)。
  • Tutorial 9.7(c):访问 0x22222000,VPN 0x22222 的有效位是 0 → 硬件不用这一行的 PPN,触发页缺失 → 操作系统调页、填 PPN、置 1 → 重新执行指令。
几个"位"别混了
位在哪里回答的问题
页表的有效位页表每一项这一页在不在物理内存里?
缓存的有效位缓存每一行这一行有没有装着数据?(空行为 0,永远不会命中)
TLB 的有效位TLB 每一项这一条翻译能不能用?
脏位缓存行、页表项这一份被改过没有?被换出时要不要写回?

页缺失(page fault)

访问 0x22222000 时,VPN = 0x22222,页表有效位是 0,这一页不在内存里,就是页缺失:

  1. 硬件发现有效位为 0,触发异常(精确中断),交给操作系统。
  2. 操作系统从磁盘把这一页读进某个空闲物理页(内存满了就先换出一页)。
  3. 填好页表:写上 PPN,有效位置 1。
  4. 回到那条指令重新执行,这次就能翻译成功。

磁盘太慢,一次页缺失要几毫秒,也就是几百万个周期。有效位的作用:标记"这一项的翻译能不能用",防止用到无效的 PPN。

和缓存对比:同一套思路,换了一层

缓存虚拟内存
上层 / 下层缓存 / 主存主存 / 磁盘
搬运单位块(几十字节)页(KB 级)
没找到叫缓存缺失(几十到几百周期)页缺失(几百万周期)
块/页能放哪直接映射、组相联或全相联全相联:任何空闲物理页都行,用页表记位置
替换谁LRU 等近似 LRU(看引用位)
写策略写直达或写回一律写回(脏位),因为每次写磁盘太慢

块放哪、怎么找、缺失时替换谁、写怎么处理,这四个问题对缓存、TLB、虚拟内存都适用,区别只在参数。缺失代价越大,就越值得用更聪明但更贵的办法,所以虚拟内存选全相联加写回。

TLB:翻译结果的"缓存"

问题:页表在主存里,CPU 每次读写内存都得先去主存读一次页表项才知道物理地址,访存次数直接翻倍。解决办法是 TLB(Translation Lookaside Buffer,快表):CPU 里一个很小、很快的缓存,专门存最近用过的几条翻译(VPN → PPN)。一般不超过 128–256 项,比数据缓存还快;和普通缓存一样可以是全相联、组相联或直接映射。每项含有效位、VPN(当作标签)、PPN、脏位、引用位、访问权限。

每次访存的翻译流程
CPU 发出虚拟地址
   │
   ├─ 查 TLB ── 命中 ──→ 直接得到物理地址 → 去缓存/主存取数据     (最常见,几乎不花时间)
   │
   └─ TLB 缺失 → 去主存读页表
                 ├─ 有效位 = 1 → 把这条翻译装进 TLB,重试       (几十个周期)
                 └─ 有效位 = 0 → 页缺失,操作系统从磁盘调页     (几百万个周期)
三种情况一定要分清
情况原因代价谁处理
TLB 命中最近用过这一页几乎为 0硬件
TLB 缺失,页在内存页在内存,只是 TLB 没记几十个周期硬件或操作系统,从页表装入 TLB
页缺失页根本不在内存几百万个周期操作系统,从磁盘调页
  • TLB 缺失不等于页缺失。TLB 缺失经常发生,大多数时候页其实就在内存里,比页缺失频繁得多。Tutorial 9.7(f):页缺失 5 ms、TLB 缺失 50 ns,前者是后者的 5×10⁻³ ÷ 5×10⁻⁸ = 100,000 倍。
  • TLB 这么小也管用,靠的还是局部性。一页 4 KB 能装 1024 个 4 字节数据,顺序访问一个数组时连续上千次访问都落在同一页,第一次查到之后全部 TLB 命中。
  • 软硬件分工(讲义):TLB 是硬件,TLB 访问时间算在缓存命中时间里(流水线可能为它多留一级);页缺失触发精确中断,由操作系统处理;硬件负责更新页表里的脏位和引用位。
打个比方

你笔记里写的"第 3 本书第 120 页"是虚拟地址,只是你自己的编号,别人的"第 3 本书"是另一本。图书馆的总目录是页表,告诉你"你的第 3 本书"实际在"B 区 17 号架",目录很厚,放在服务台,要走过去查。贴在桌上的便利贴是 TLB,记着最近查过的几本书在哪个架子,先看便利贴,有就不用去服务台。书架上根本没这本书、要从仓库调过来,就是页缺失,最慢。"第 120 页"不需要翻译,就是页内偏移。

例题:一次走完 TLB 命中、TLB 缺失和页缺失

例题 9.7虚拟地址翻译 + TLB

某系统虚拟地址 16 位,物理地址 15 位,页大小 4 KB。TLB 只有 2 项,全相联,LRU 替换;初始 TLB 里是 VPN 2 → PPN 0 和 VPN 5 → PPN 1,其中 VPN 5 最久未用。页表如下(其余 VPN 都无效):

VPNPPN有效位
041
1—0(在磁盘上)
201
331
4—0
511

发生页缺失时,操作系统把那一页调入空闲的物理页 6,处理完后把这条翻译装进 TLB。依次访问虚拟地址:0x2ABC, 0x3010, 0x2FFF, 0x1234, 0x3FF0, 0x5008。

  1. 页内偏移、VPN、PPN 各几位?
  2. 对每次访问写出 VPN、偏移、TLB 命中还是缺失、是否页缺失、物理地址,以及访问后 TLB 里的内容。
  3. 查 TLB 每次 1 周期,TLB 缺失时读页表 50 周期,页缺失 1,000,000 周期。这 6 次访问的翻译总代价是多少周期?
  4. 思考:TLB 改成 4 项,结果有什么变化?
解答 · 逐步

(a) 偏移 = log₂4096 = 12 位;VPN = 16 − 12 = 4 位(共 16 个虚拟页);PPN = 15 − 12 = 3 位(物理页 0–7)。所以虚拟地址的第 1 个十六进制位就是 VPN,后 3 位是偏移;物理地址的第 1 个十六进制位就是 PPN。

#虚拟地址VPN偏移TLB查页表物理地址访问后 TLB(左边是较久未用的)
10x2ABC2ABC命中不用查0x0ABC5→1, 2→0
20x30103010缺失PPN 3,有效;装入,踢掉最久未用的 VPN 50x30102→0, 3→3
30x2FFF2FFF命中不用查0x0FFF3→3, 2→0
40x12341234缺失有效位 0 → 页缺失;调入物理页 6,页表改为 1 → 6;装入 TLB,踢掉 VPN 30x62342→0, 1→6
50x3FF03FF0缺失PPN 3,有效(第 4 步 VPN 3 被踢出 TLB,但页还在内存);踢掉 VPN 20x3FF01→6, 3→3
60x50085008缺失PPN 1,有效(VPN 5 第 2 步被踢出 TLB);踢掉 VPN 10x10083→3, 5→1

结果:TLB 命中 2 次(第 1、3 步),TLB 缺失 4 次;其中只有第 4 步是页缺失。第 5、6 步是 TLB 缺失但不是页缺失:页一直在内存里,只是翻译被挤出了 TLB,查一次页表就行。

(c) 每次都先查 TLB:6 × 1 = 6  4 次 TLB 缺失读页表:4 × 50 = 200  1 次页缺失:1 × 1,000,000 = 1,000,000  总代价 = 6 + 200 + 1,000,000 = 1,000,206 周期

一次页缺失占了 99.98% 的时间,这就是为什么页缺失必须非常少,也是虚拟内存要用全相联和写回、尽量减少页缺失的原因。

(d) TLB 改成 4 项:初始的 2、5 加上后来的 3、1 正好 4 项,不需要踢任何一项,第 5、6 步变成命中。TLB 命中 4 次、缺失 2 次(第 2、4 步),总代价 = 6 + 2 × 50 + 1,000,000 = 1,000,106 周期。TLB 太小会把还要用的翻译挤出去,和缓存的容量缺失是同一回事。

交互实验 09
地址翻译全流程动画

用上面例题的 6 次访问,一步步演示虚拟地址怎么变成物理地址:先拆出 VPN 和偏移,偏移直接抄过去;VPN 先查 TLB,命中就直接得到 PPN;缺失就去主存读页表;有效位为 0 再去磁盘调页。小方块是当前正在传递的东西,右下角累计翻译花掉的周期数。做完例题再看,可以逐步对答案。

CPU发出虚拟地址 虚拟地址 VA(16 位) VPN 偏移 TLB(快表)查一次 1 周期 物理地址 PA(15 位) PPN 偏移 主存 Main memory 页表(查一次约 50 周期) VPNPPN有效位 物理页(数据所在) 磁盘VPN 1、4 的页调页约1,000,000 周期
#虚拟地址VPN偏移TLB页缺失物理地址

在 9.3 节的"地址拆分器"里切到"虚拟地址"标签,点"9.7 节例题"预设,也可以逐个检查这道题每个地址的拆分和翻译(那个工具只查页表,不模拟 TLB;0x1234 会显示页缺失,因为它用的是调页之前的页表)。

考试要点(Tutorial 9.7)
  1. 位数:偏移 = log₂(页大小),VPN = 虚拟地址位 − 偏移,PPN = 物理地址位 − 偏移。
  2. 翻译:拆出 VPN 查表换成 PPN,偏移照抄;4 KB 页就是后 3 个十六进制位不动。
  3. 有效位 = 0 → 页缺失,说清操作系统做的四步。
  4. 偏移为什么不变:页和页框大小相同、按页对齐。
  5. 虚拟内存的优点任选两个:运行比内存大的程序、进程隔离与保护、可控共享、重定位方便、避免碎片。
  6. TLB 缺失 vs 页缺失:代价差好几个数量级。

自测 · Self-Check

32 位地址、64 KB 缓存、64 B 块。直接映射、4 路、全相联的 Tag / Index / Offset 各几位?
块数 1024,Offset 6。直接映射:1024 组,Index 10,Tag 16。4 路:256 组,Index 8,Tag 18。全相联:1 组,Index 0,Tag 26。(Tutorial 9.5)
为什么固定容量下块不能无限加大?
容量固定时块越大块数越少,能同时留在缓存里的不同区域越少,冲突和容量缺失增加;而且每次缺失要传的字节更多,缺失代价上升,很多取回来的字节可能用不到。所以缺失率随块大小先降后升,AMAT 也有最低点,常见最佳值 64–128 B。
写回缓存里,对同一块连写 3 次,主存一共被写几次?写直达呢?
写回:只在块被替换时写回 1 次(脏位为 1)。写直达:每次写都更新主存,3 次。
L1 局部缺失率 5%,L2 局部缺失率 2%。L2 的全局缺失率是多少?代表什么?
5% × 2% = 0.1%。意思是 CPU 发出的所有访问中,有 0.1% 在 L1 和 L2 都缺失,最终要去主存。
TLB 缺失一定意味着页缺失吗?
不一定。TLB 只缓存最近的翻译。如果页在内存里只是 TLB 没记,就从页表把翻译装进 TLB(几十个周期);只有页表项有效位为 0、页根本不在内存时才是页缺失(百万周期级,由操作系统处理)。
LESSON 10

流水线入门与 DLX 指令集

Final 范围

Topic II 从这里开始。缓存解决的是"等内存",流水线解决的是"一条指令做完才开始下一条"。这一课先用延迟和吞吐量两个指标说清楚流水线到底快在哪里,给出流水线总时间公式;然后介绍后面三课都要用的教学处理器 DLX:寄存器、指令、寻址模式和 R/I/J 三种编码格式。

本课课堂练习 · Exercises10.110.210.310.410.510.610.710.810.9

10.1 延迟、吞吐与关键路径

  • 延迟(latency):一个工作单位从进入系统到完成所需的时间。
  • 吞吐量(throughput):单位时间内系统能完成的工作单位数。
  • 并行的目标:提高吞吐量;有时也能降低延迟。

组合电路的延迟由关键路径决定:从输入到输出所有路径中门延迟之和最大的那条。讲义的 1 位全加器:AND、OR、非门各 tg,XOR 为 3tg,关键路径 A → XOR → XOR → S,延迟 6tg,吞吐量 = 每 6tg 完成一次加法 = 1/(6tg)。

找关键路径的方法

从输入开始,给每个门的输出标上"最早稳定时间" = 它所有输入的最晚稳定时间 + 本门延迟(输入端的小圆圈是非门,要单独加一个非门延迟)。输出端的时间就是延迟,倒推取最大值的那条输入就是关键路径。Exercise 10.1、10.2 的完整解答见习题小程序。

课堂练习 · In-class exercises
Exercise 10.1门电路关键路径(一) · Critical path of a gate circuit (1)
EN

For the logic circuit below, (a) determine and clearly show the critical path delay using the gate delays NOT = 9, AND = 13, OR = 11, XOR = 23; (b) compute the latency and throughput of the circuit.

中

对下面的电路:(a) 用门延迟 NOT = 9、AND = 13、OR = 11、XOR = 23 找出并标明关键路径延迟;(b) 求电路的延迟和吞吐量。

ZABCDXOR · 23AND · 13OR · 11NOT · 9OR · 11OR · 11XOR · 23

Boxes show gate type · delay 方框内为门类型 · 延迟

解答 · Solution
ZABCDXOR · 23t = 23AND · 13t = 13OR · 11t = 11NOT · 9t = 20OR · 11t = 31OR · 11t = 34XOR · 23t = 57

Highlighted = critical path 高亮为关键路径;t = arrival time 到达时间

Path 路径Delay 延迟
A → OR → XOR11 + 23 = 34
C/D → OR → NOT → OR → XOR11 + 9 + 11 + 23 = 54
B/C → AND → OR → XOR13 + 11 + 23 = 47
B/D → XOR → OR → XOR23 + 11 + 23 = 57
EN

Critical path: B (or D) → XOR → OR → XOR → Z, delay = 57 time units. One result is produced every 57 units, so throughput = 1/57 computations per unit time.

Method: label each gate output with its earliest settle time = latest input settle time + gate delay; the time at the output is the latency.

中

关键路径:B(或 D)→ XOR → OR → XOR → Z,延迟 = 57 个时间单位。电路每 57 个单位完成一次计算,吞吐量 = 1/57(每单位时间完成的计算次数)。

方法:给每个门的输出标"最早稳定时间" = 各输入最晚稳定时间 + 本门延迟,输出端的时间就是延迟。

Exercise 10.2门电路关键路径(二) · Critical path of a gate circuit (2)
EN

For the circuit below (a small circle at a gate input is an inverter, i.e. a NOT gate), (a) determine and clearly show the critical path delay using NOT = 10, AND = 15, OR = 17; (b) compute the latency and throughput.

中

对下面的电路(门输入端的小圆圈表示非门):(a) 用 NOT = 10、AND = 15、OR = 17 找出并标明关键路径延迟;(b) 求延迟和吞吐量。

fabcdNOT · 10NOT · 10AND · 15AND · 15AND · 15AND · 15OR · 17OR · 17NOT · 10NOT · 10AND · 15AND · 15AND · 15OR · 17

Boxes show gate type · delay 方框内为门类型 · 延迟

解答 · Solution
fabcdNOT · 10t = 10NOT · 10t = 10AND · 15t = 25AND · 15t = 25AND · 15t = 15AND · 15t = 25OR · 17t = 42OR · 17t = 42NOT · 10t = 10NOT · 10t = 10AND · 15t = 57AND · 15t = 25AND · 15t = 57OR · 17t = 74

Highlighted = critical path 高亮为关键路径;t = arrival time 到达时间

EN
a/b → NOT → AND → OR → AND → OR → f= 10 + 15 + 17 + 15 + 17 = 74c/d → NOT → AND → OR = 10 + 15 + 17 = 42 (shorter)

Latency = 74, throughput = 1/74. Do not forget the NOT delay hidden in the input bubbles.

中
a/b → NOT → AND → OR → AND → OR → f= 10 + 15 + 17 + 15 + 17 = 74c/d → NOT → AND → OR = 10 + 15 + 17 = 42(较短)

延迟 74,吞吐量 1/74。别漏掉输入端小圆圈代表的非门延迟。

10.2 空间并行 vs 时间并行

  • 空间并行:多套硬件同时工作,同时处理多个任务。通常直接叫"并行"。
  • 时间并行:把任务切成几级(像流水线工厂),不同任务同时处在不同级,执行在时间上重叠。这就是流水线。

讲义的饼干例子:擀饼干 5 分钟,烤 15 分钟。一盘的延迟永远是 20 分钟(1/3 小时),不管用哪种并行。

方案吞吐量说明
Ben 一个人,烤完再做下一盘3 盘/小时每 20 分钟一盘
空间并行:Alyssa 带着自己的烤箱帮忙6 盘/小时两人各 20 分钟一盘
时间并行:第一盘进烤箱就开始擀第二盘→ 4 盘/小时n 盘用时 5 + 15n 分钟,n→∞ 时每 15 分钟一盘(瓶颈是烤箱)
两人各自流水8 盘/小时2 × 4

讲义说本课重点放在流水线,因为它是 CPU 设计的基础,又能自然引出 CPI、冒险这些概念;空间并行还牵涉同步、通信、内存一致性,复杂得多。

10.3 流水线总时间公式

洗衣房例子:4 个人各有一筐衣服,洗 30 分钟、烘 40 分钟、叠 20 分钟。顺序做要 4 × 90 = 360 分钟(6 小时)。流水起来,一台机器空出来就接下一筐:

Ttotal = Tfirst + (n − 1) × Tcycle Tfirst = ΣTi(第一个任务走完所有级) Tcycle = max Ti(最慢的一级) 洗衣房:30 + 40 + 20 + (4 − 1) × 40 = 210 分钟

如果每级时间相同、各占 1 个周期,k 级流水线处理 n 个任务需要 k + n − 1 个周期(Tutorial 10.1:第一个任务 k 个周期出来,之后每周期出一个)。

流水线的几条结论

  • 流水线不降低单个任务的延迟,提高的是整批工作的吞吐量。
  • 吞吐量受最慢的一级限制。
  • 理论最大加速比约等于级数;各级时间不均衡会降低加速比。
  • 填充(filling)和排空(draining)流水线的时间也会拉低加速比,任务越多这部分影响越小。
两种算法,看题目设定

讲义里出现了两种模型。任务流水(洗衣房、Tutorial 10.5/10.6 写明"一级做完就能立刻接下一个任务"):第一个任务只需 ΣTi,用上面的公式。时钟流水(处理器、DLX):每级都被拉长到一个时钟周期,第一条指令的延迟 = 级数 × 时钟周期(讲义 5 × 250 = 1250 ps),总时间 = (k + n − 1) × Tclk。n 很大时两者几乎相等,加速比都趋近 ΣTi / max Ti。

交互实验 05
流水线计时器

10.4 单周期 vs 流水线处理器

讲义的五级时间:取指 250 ps、译码读寄存器 150、执行 200、访存 250、写寄存器 100。

单周期处理器五级流水线处理器
时钟周期950 ps(五级之和)250 ps(最慢一级)
单条指令延迟950 ps5 × 250 = 1250 ps(变长了)
吞吐量1/950 ps ≈ 1.05×10⁹ 条/秒1/250 ps = 4×10⁹ 条/秒
大量指令时的加速比950 / 250 = 3.8(不是 5,因为各级不均衡)
讲义的笔误

讲义第 22 页把单周期吞吐量写成 1.59 × 10⁹ 条/秒,第 25 页写"每 950 ns 一条"。正确值是 1/950 ps ≈ 1.05 × 10⁹ 条/秒,单位是 ps。

Tutorial 10.7(d) 问"流水线的单条延迟更长,所以流水线更慢"对不对:不对。延迟只描述一条指令,处理器跑的是成千上万条指令的程序,决定程序运行时间的是吞吐量。

课堂练习 · In-class exercise
Exercise 10.3单周期 vs 五级流水 · Single-cycle vs 5-stage pipeline
EN

Stage delays: IF 180 ps, ID 220 ps, EX 250 ps, MEM 300 ps, WB 150 ps. (a) For a single-cycle processor, find the instruction latency and the throughput. (b) For a 5-stage pipeline, find the clock period, the instruction latency and the throughput once the pipeline is full. (c) For a very large program, find the speedup of pipelining. (d) Explain why the speedup is less than the number of stages.

中

各级延迟:IF 180、ID 220、EX 250、MEM 300、WB 150 ps。(a) 单周期处理器的单条延迟和吞吐量;(b) 五级流水线的时钟周期、单条延迟、填满后的吞吐量;(c) 程序很大时的加速比;(d) 为什么加速比小于级数?

解答 · Solution
EN
(a) Latency = 180 + 220 + 250 + 300 + 150 = 1100 ps; throughput = 1 / 1100 ps ≈ 9.09 × 10⁸ instr/s(b) Clock = slowest stage = 300 ps; latency = 5 × 300 = 1500 ps; throughput = 1 / 300 ps ≈ 3.33 × 10⁹ instr/s(c) Speedup = 1100 / 300 ≈ 3.67

(d) The stages are unbalanced: the clock is set by the slowest stage (MEM, 300 ps) while the others idle (the average stage is only 220 ps). Pipeline-register overhead, fill/drain time and hazard stalls reduce it further.

中
(a) 延迟 = 180 + 220 + 250 + 300 + 150 = 1100 ps;吞吐 = 1 / 1100 ps ≈ 9.09 × 10⁸ 条/秒(b) 时钟 = 最慢级 = 300 ps;延迟 = 5 × 300 = 1500 ps;吞吐 = 1 / 300 ps ≈ 3.33 × 10⁹ 条/秒(c) 加速比 = 1100 / 300 ≈ 3.67

(d) 各级不均衡:时钟由最慢的 MEM(300 ps)决定,其他级在空等(平均每级只有 220 ps)。此外还有流水线寄存器开销、填充排空和冒险停顿。

10.5 DLX 寄存器、数据类型与指令

DLX(读作 "deluxe")是教学用的简化 RISC 处理器,结构和 MIPS 很像,五级流水线:取指、译码、执行、访存、写回。和 MIPS 相比,DLX 指令更少、寻址模式更简单,冒险处理也用教学化的例子。

  • 32 个 32 位通用寄存器 R0–R31。R0 恒为 0(只读,写它无效);R31 是链接寄存器,JAL/JALR 把返回地址存在这里。
  • 数据类型:整数 B(8 位字节)、H(16 位半字)、W(32 位字),U 表示无符号;浮点 F(32 位单精度)、D(64 位双精度);HI/LO 存放扩展结果的高、低 32 位。
  • 立即数是 16 位,嵌在 I 型指令里(ADDI、LW 等)。
常用 DLX 指令
类别指令例子与语义
取数LB/LBU, LH/LHU, LW, LF/LD, LHILW R5, 400(R3):R5 ← Mem[R3 + 400]
存数SB, SH, SW, SF/SDSW R4, 400(R3):Mem[R3 + 400] ← R4。存数没有无符号版本,因为写进内存的只是比特,有无符号只影响读出时的解释
算术ADD, SUB, ADDU, SUBU
ADDI, SUBI, ADDUI, SUBUI
SUBI R4, R2, #10:R4 ← R2 − 10。U = 无符号(不产生溢出异常),I = 立即数
逻辑AND/ANDI, OR/ORI, XOR/XORIANDI 清位、ORI 置位、XORI 翻转位
置条件SLT, SLE, SGT, SGE, SEQ, SNE(及 I 版本)SLT R1, R2, R3:R2 < R3 则 R1 ← 1,否则 0。常配合 BEQZ/BNEZ 使用
条件分支BEQZ, BNEZBEQZ R7, Loop1:R7 = 0 就跳到 Loop1,否则顺序执行
无条件跳转J, JR, JAL, JALRJ 跳到标号;JR 跳到寄存器里的地址;JAL/JALR 跳之前先 R31 ← PC + 4

位操作例子:R1 = 10110011₂,ANDI R2, R1, #254(掩码 11111110)清第 0 位 → 10110010;ORI R2, R2, #8 置第 3 位 → 10111010;XORI R2, R2, #2 翻转第 1 位 → 10111000。

函数调用:JAL SUM 先存 R31 ← PC + 4 再跳到 SUM,函数结尾 JR R31 回到 JAL 的下一条。JALR 用于函数指针、虚函数这类运行时才确定目标的调用。

10.6 寻址模式与 load-store 架构

寻址模式例子操作数在哪
寄存器寻址ADD R1, R2, R3都在寄存器里,不访存,最快
立即数寻址ADDI R2, R1, #3一个操作数是指令里的常数
偏移(基址)寻址LW R17, 400(R23)有效地址 = 基址寄存器 + 偏移(以字节计),用于数组、结构体
PC 相对BEQZ R2, label、J label目标 = PC + 4 + 偏移(立即数字段当作分支位移)
DLX 是 load-store 架构

只有 load/store 能访问内存,ALU 指令只能操作寄存器。所以 ADD R3, R2, 256(R1) 是非法的,必须拆成两条:LW R4, 256(R1) 把数据取进寄存器,再 ADD R3, R2, R4。Tutorial 10.8(d) 考的就是这个。

注意存数指令有两种写法:讲义写 SW R4, 400(R3),Tutorial 写 SW 20(R2), R3(经典 DLX 语法)。两者意思相同:把寄存器的值存到"基址 + 偏移"处。

课堂练习 · In-class exercises
Exercise 10.4算术指令的结果 · Results of arithmetic instructions
EN

R1 = 10, R2 = 20, R3 = 5, R4 = 100. Determine the value written to the destination register (each instruction executes independently): (a) ADD R5, R1, R2 (b) SUB R6, R2, R3 (c) ADDI R7, R1, 15

中

R1 = 10、R2 = 20、R3 = 5、R4 = 100。各指令独立执行,求写入目的寄存器的值:(a) ADD R5, R1, R2 (b) SUB R6, R2, R3 (c) ADDI R7, R1, 15

解答 · Solution
EN

(a) R5 = 10 + 20 = 30 (b) R6 = 20 − 5 = 15 (c) R7 = 10 + 15 = 25

中

(a) R5 = 10 + 20 = 30 (b) R6 = 20 − 5 = 15 (c) R7 = 10 + 15 = 25

Exercise 10.5指令类型与所用流水级 · Instruction type and stages used
EN

For LW R1, 0(R2), ADD R3, R1, R4, SW R3, 8(R2), BEQZ R3, LOOP on the 5-stage DLX pipeline (IF → ID → EX → MEM → WB): identify R-type or I-type, the stages used, whether a register is written back, and briefly describe the operation.

中

对 LW R1, 0(R2)、ADD R3, R1, R4、SW R3, 8(R2)、BEQZ R3, LOOP(五级 DLX):判断 R 型或 I 型、用到的流水级、是否写回寄存器,并简述操作。

解答 · Solution
Instruction 指令Type 类型Stages 流水级Writes reg? 写寄存器Operation 操作
LW R1,0(R2)IIF ID EX MEM WBYes 是(R1)R1 ← Mem[R2 + 0]
ADD R3,R1,R4RIF ID EX (MEM pass) WBYes 是(R3)R3 ← R1 + R4
SW R3,8(R2)IIF ID EX MEMNo 否Mem[R2 + 8] ← R3
BEQZ R3,LOOPIIF ID EX MEM (branch completion)No 否if R3 = 0 then PC ← LOOP
EN

Only LW uses all five stages for real work. ADD passes through MEM doing nothing; SW writes memory but no register; BEQZ completes the branch in MEM (updates the PC) and writes no register.

中

只有 LW 走满五级并且真正用到 MEM 和 WB;ADD 的 MEM 级空过;SW 写存储器但不写寄存器;BEQZ 在 MEM 级完成分支(更新 PC),也不写回。

Exercise 10.6一段程序的数值追踪 · Tracing a short program
EN

R1 = 10, R2 = 100, R3 = 20, R4 = 5, Mem[104] = 30. Program: LW R5, 4(R2); ADD R6, R5, R1; SUB R7, R6, R4; SW R7, 8(R2). (a) Effective address of LW. (b) Value loaded into R5. (c) Values in R6 and R7. (d) Memory location and value written by SW. (e) Pipeline stages used by each instruction.

中

R1 = 10、R2 = 100、R3 = 20、R4 = 5,Mem[104] = 30。程序:LW R5, 4(R2);ADD R6, R5, R1;SUB R7, R6, R4;SW R7, 8(R2)。求 (a) LW 的有效地址;(b) R5;(c) R6、R7;(d) SW 写到哪个地址、写什么值;(e) 每条用到的流水级。

解答 · Solution
EN
(a) 100 + 4 = 104(b) R5 = Mem[104] = 30(c) R6 = 30 + 10 = 40; R7 = 40 − 5 = 35(d) Mem[100 + 8] = Mem[108] ← 35

(e) LW: IF ID EX MEM WB; ADD and SUB: IF ID EX WB (MEM passes through); SW: IF ID EX MEM.

中
(a) 100 + 4 = 104(b) R5 = Mem[104] = 30(c) R6 = 30 + 10 = 40;R7 = 40 − 5 = 35(d) Mem[100 + 8] = Mem[108] ← 35

(e) LW:IF ID EX MEM WB;ADD、SUB:IF ID EX WB(MEM 空过);SW:IF ID EX MEM。

10.7 R / I / J 三种指令格式

DLX 指令格式(32 位)
I 型 Opcode 6 Rs 5 Rt 5 Immediate 16 R 型 Opcode 6 Rs 5 Rt 5 Rd 5 Function 11 J 型 Opcode 6 Offset added to PC 26 位 31 ←→ 位 0
格式用于要点
I 型立即数运算、load、store、条件分支、JR、JALR一个源/基址寄存器 Rs + 一个 Rt + 16 位立即数。Rt 不一定是目的寄存器:LW 时 Rt 接收数据,SW 时 Rt 提供数据,ADDI 时 Rt 接收结果。立即数的含义随指令变:算术里是常数,load/store 里是地址偏移,分支里是位移
R 型寄存器-寄存器 ALU 运算(ADD、SUB、AND、OR、SLT…)Rs、Rt 永远是两个源操作数,Rd 永远接收结果;Opcode 只说明是 ALU 类,Function 字段才指定具体做哪种运算
J 型J、JAL(以及 trap、异常返回)只有 Opcode 和 26 位偏移,不读寄存器;只有 JAL 会写寄存器(R31)
快速判格式

有三个寄存器、没有常数 → R 型。有一个常数、偏移或标号,且有寄存器 → I 型(包括 BEQZ/BNEZ、LW/SW、所有带 I 的运算)。只有标号、没有寄存器 → J 型(J、JAL)。

课堂练习 · In-class exercises
Exercise 10.7判断指令格式 · Identify the instruction format
EN

Identify whether each instruction uses the R-type, I-type or J-type format: ADD R1,R2,R3, LW R4,8(R5), J LOOP, ADDI R6,R7,#10, SUB R8,R9,R10, BEQZ R1,EXIT, JAL FUNCTION, SW R3,16(R4).

中

判断格式(R / I / J):ADD R1,R2,R3、LW R4,8(R5)、J LOOP、ADDI R6,R7,#10、SUB R8,R9,R10、BEQZ R1,EXIT、JAL FUNCTION、SW R3,16(R4)。

解答 · Solution
EN

R-type: ADD, SUB (three registers). I-type: LW, ADDI, BEQZ, SW (register + immediate / offset / displacement). J-type: J, JAL (26-bit offset only).

中

R 型:ADD、SUB(三个寄存器)。I 型:LW、ADDI、BEQZ、SW(寄存器 + 立即数/偏移/位移)。J 型:J、JAL(只有 26 位偏移)。

Exercise 10.8填写各字段 · Fill in the format fields
EN

For each instruction, fill in the fields used by its format (Opcode, Rs, Rt, Rd, Immediate/Target): ADDI R1,R2,#12, LW R3,40(R4), ADD R5,R6,R7, J LOOP, plus SUB R8,R9,R10 and BEQZ R1,EXIT.

中

填写每条指令格式中用到的字段(Opcode、Rs、Rt、Rd、立即数/目标):ADDI R1,R2,#12、LW R3,40(R4)、ADD R5,R6,R7、J LOOP,以及 SUB R8,R9,R10、BEQZ R1,EXIT。

解答 · Solution
Instruction 指令OpcodeRsRtRdImm / Target 立即数/目标
ADDI R1,R2,#12ADDIR2R1—12
LW R3,40(R4)LWR4R3—40
ADD R5,R6,R7ALU (func = ADD)R6R7R5—
SUB R8,R9,R10ALU (func = SUB)R9R10R8—
BEQZ R1,EXITBEQZR1——offset to EXIT 到 EXIT 的位移
J LOOPJ———LOOP (26-bit offset)
EN

In I-type instructions the role of Rt depends on the instruction: for ADDI and LW it is the destination. For R-type, the opcode only says "ALU operation"; the function field selects the exact operation.

中

I 型里 Rt 的角色随指令变化:ADDI、LW 中 Rt 是目的寄存器。R 型的 Opcode 只说明是 ALU 类,具体运算由 Function 字段指定。

Exercise 10.9读写存储器与寄存器 · Memory and register access
EN

Program: LW R1, 0(R2); ADD R3, R1, R4; SW R3, 8(R2); J DONE. For each instruction state the format and whether it reads memory, writes memory and writes a register.

中

程序:LW R1, 0(R2);ADD R3, R1, R4;SW R3, 8(R2);J DONE。对每条写出格式,以及是否读存储器、写存储器、写寄存器。

解答 · Solution
Instruction 指令Format 格式Reads mem 读存储器Writes mem 写存储器Writes reg 写寄存器
LW R1,0(R2)IYes 是No 否Yes 是
ADD R3,R1,R4RNo 否No 否Yes 是
SW R3,8(R2)INo 否Yes 是No 否
J DONEJNo 否No 否No 否
EN

"Memory" here means data memory; every instruction still reads instruction memory in IF.

中

这里的"读写存储器"指数据存储器;每条指令在 IF 都要读指令存储器。

自测 · Self-Check

3 级流水线、每级 1 周期。至少要处理多少个任务,才能比顺序执行快 2.5 倍以上?
顺序 3n 周期,流水 n + 2 周期。3n/(n + 2) ≥ 2.5 → 0.5n ≥ 5 → n ≥ 10。n = 10 时恰好 30/12 = 2.5(Tutorial 10.1)。
五级流水线各级 220/180/250/200/150 ps。时钟周期、单条延迟、吞吐量、各级完全平衡时的最大加速比?
时钟 250 ps;延迟 5 × 250 = 1250 ps;吞吐量 1/250 ps = 4×10⁹ 条/秒;总和 1000 ps,平衡后每级 200 ps,最大加速比 1000/200 = 5(Tutorial 10.7)。
为什么把 ALU 那一级从 200 ps 缩短到 150 ps,流水线加速比反而从 4 降到 3.75?
ALU 不是瓶颈:流水线时钟仍由 200 ps 的取指/访存决定,流水线速度没变;但单周期处理器的时间从 800 降到 750 ps,被比较的对象变快了,所以比值变小(Tutorial 10.3a)。
BEQZ R3, LOOP 和 J EXIT 分别是什么格式?
BEQZ 是 I 型(一个寄存器 + 立即数位移);J 是 J 型(只有 26 位偏移)。
SW R1, 40(R2) 里 Rt 是谁?它是目的寄存器吗?
Rt = R1,Rs = R2,立即数 40。这里 Rt 不是目的:它提供要存进内存的数据,有效地址是 R2 + 40。SW 不写任何寄存器。
LESSON 11

五级 DLX 流水线

Final 范围

这一课把一条 DLX 指令拆成五个时钟步骤,逐个看数据通路里每个部件在干什么、产生什么值、存进哪个临时寄存器。Tutorial 11 几乎全是这种"填表题":给一条指令和寄存器初值,写出每一级的操作、关键输出和流水线寄存器内容。把下面的实验跑熟,这类题就是送分题。

本课课堂练习 · Exercises11.111.211.311.411.511.611.711.8

11.1 五个步骤的微操作

步骤名称微操作(RTL)说明
IF取指IR ← Mem[PC]
NPC ← PC + 4
用 PC 从指令存储器取指令;加法器同时算下一条地址(每条指令 4 字节)
ID译码 / 读寄存器A ← Regs[Rs]
B ← Regs[Rt]
Imm ← SignExt(imm16)
b ← 目的寄存器号
译码、读源寄存器、符号扩展立即数、产生控制信号,三件事并行(固定字段译码)。b 是目的寄存器的编号:R 型是 Rd,I 型是 Rt
EX执行 / 算地址load/store:ALUOut ← A + Imm
R 型:ALUOut ← A op B
I 型运算:ALUOut ← A op Imm
分支:ALUOut ← NPC + Imm;Cond ← (A == 0)
ALU 要么算结果,要么算有效地址,要么算分支目标;Zero? 单元判断分支条件(BEQZ 判 A = 0,BNEZ 判 A ≠ 0)
MEM访存 / 完成分支load:LMD ← Mem[ALUOut]
store:Mem[ALUOut] ← B
分支:PC ← Cond ? ALUOut : NPC
只有 load/store 访问数据存储器;分支在这里更新 PC;ALU 指令直接穿过
WB写回R 型:Regs[Rd] ← ALUOut
I 型运算:Regs[Rt] ← ALUOut
load:Regs[Rt] ← LMD
WB 前的 MUX 选 ALUOut(ALU 指令)或 LMD(load)。store 和分支不写回

讲义的 MEM 例子:SW R3, 500(R4),R4 = 100、R3 = 99:EX 算出 ALUOut = 600,MEM 执行 Mem[600] ← 99。LW R1, 30(R2),R2 = 100、Mem[130] = 55:ALUOut = 130,LMD = 55。BEQZ R4, LOOP,R4 = 0、NPC = 104、偏移 40:ALUOut = 144,Cond = 1,MEM 阶段 PC ← 144。

11.2 补码与符号扩展

I 型指令的立即数只有 16 位,进 ALU 前要扩展成 32 位。符号扩展就是把第 15 位(符号位)复制到高 16 位:正数前面补 0,负数前面补 1,数值不变。

+12 = 0000 0000 0000 1100 取反 = 1111 1111 1111 0011 加 1 = 1111 1111 1111 0100 = 0xFFF4 = −12(16 位) 符号扩展 = 1111 1111 1111 1111 1111 1111 1111 0100 = 0xFFFFFFF4

Tutorial 11.2 用的就是 ADDI R5, R2, #-12(R2 = 40):A = 40,Imm = −12(0xFFFFFFF4),ALUOut = 40 + (−12) = 28,WB 写 R5 ← 28。在下面的实验里选这个预设,可以看到每一位。

课堂练习 · In-class exercises
Exercise 11.1ADDI 的五级 · ADDI through the five stages
EN

Consider ADDI R5, R2, #12 with R2 = 20. For each stage (IF, ID, EX, MEM, WB): describe the operation, give the key signal values, and state whether memory is accessed.

中

ADDI R5, R2, #12,R2 = 20。对每一级(IF、ID、EX、MEM、WB)写出所做操作、关键信号值,以及是否访问存储器。

解答 · Solution
Stage 级Operation 操作Key output 关键输出Memory 存储器
IFIR ← Mem[PC]; NPC ← PC + 4IR = ADDI R5,R2,#12; NPC = PC + 4Instruction memory 指令存储器
IDA ← Regs[R2]; Imm ← SignExt(12); b ← Rt = R5A = 20, Imm = 12, b = R5—
EXALUOut ← A + Imm (MUX selects Imm)ALUOut = 32—
MEMno data access, pass ALUOut 不访存,直接传递ALUOut = 32No 否
WBRegs[R5] ← ALUOutR5 = 32—
EN

Data memory is never accessed; only IF reads instruction memory. For an I-type ALU instruction the destination register is Rt (R5).

中

数据存储器全程没有被访问,只有 IF 读了指令存储器。I 型运算指令的目的寄存器是 Rt(R5)。

Exercise 11.2LW 的关键数值 · Key values for LW
EN

LW R1, 30(R2) with R2 = 1000 and Mem[1030] = 500. Determine (1) A after ID, (2) Imm after sign extension, (3) ALUOut after EX, (4) the memory location accessed in MEM, (5) the value loaded into LMD, (6) the value written into R1 in WB.

中

LW R1, 30(R2),R2 = 1000,Mem[1030] = 500。求 (1) ID 后的 A;(2) 符号扩展后的 Imm;(3) EX 后的 ALUOut;(4) MEM 访问的地址;(5) LMD;(6) WB 写入 R1 的值。

解答 · Solution
EN

(1) A = 1000 (2) Imm = 30 (3) ALUOut = 1030 (4) address 1030 (5) LMD = 500 (6) R1 = 500

中

(1) A = 1000 (2) Imm = 30 (3) ALUOut = 1030 (4) 地址 1030 (5) LMD = 500 (6) R1 = 500

Exercise 11.4ADD 与 LW 对比 · ADD vs LW
EN

For ADD R1,R2,R3 and LW R1,40(R2): which stages are used, is data memory accessed, what does the ALU do, what value is written back, and which MUX input is selected in EX?

中

对 ADD R1,R2,R3 和 LW R1,40(R2):用到哪些级?是否访问数据存储器?ALU 做什么?写回什么值?EX 级的 MUX 选哪个输入?

解答 · Solution
ADD R1,R2,R3LW R1,40(R2)
Stages 级IF ID EX (MEM) WBIF ID EX MEM WB
ALU inputs ALU 输入A = R2, B = R3 (MUX → B)A = R2, Imm = 40 (MUX → Imm)
ALU operation ALU 操作add → result 加法得结果add → effective address 加法得有效地址
Data memory 数据存储器No 否Read 读
Written back 写回ALUOut = R2 + R3LMD = Mem[R2 + 40]
EN

Both add in the ALU; the difference is the second ALU input (B for R-type, Imm for loads/stores) and the WB multiplexer (ALUOut for ADD, LMD for LW).

中

两条都在 ALU 里做加法,区别在第二个输入:R 型选 B,load/store 选 Imm;写回时 WB 的 MUX 一个选 ALUOut,一个选 LMD。

Exercise 11.5BEQZ 的目标与条件 · BEQZ target and condition
EN

Register R2 contains 0 and PC = 1000. Consider BEQZ R3, +40. Determine (1) NPC, (2) the ALU inputs, (3) the branch target address, (4) the output of the Zero? block, (5) whether the branch is taken, (6) the new PC.

中

R2 = 0,PC = 1000。对 BEQZ R3, +40 求 (1) NPC;(2) ALU 输入;(3) 分支目标地址;(4) Zero? 的输出;(5) 是否跳转;(6) 新 PC。

解答 · Solution
EN

The question gives R2 = 0 but the branch tests R3, which looks like a typo; assume R3 = 0:

(1) NPC = 1000 + 4 = 1004(2) ALU inputs: NPC = 1004 and Imm = 40(3) Target = 1004 + 40 = 1044(4) Zero? = 1(5) Taken(6) New PC = 1044

If R3 ≠ 0: Zero? = 0, not taken, new PC = NPC = 1004.

中

题目给的是 R2 = 0,但指令测试 R3,应是笔误,按 R3 = 0 理解:

(1) NPC = 1000 + 4 = 1004(2) ALU 输入:NPC = 1004 与 Imm = 40(3) 目标 = 1004 + 40 = 1044(4) Zero? = 1(5) 跳转(6) 新 PC = 1044

若 R3 ≠ 0:Zero? = 0,不跳转,新 PC = NPC = 1004。

11.3 为什么所有指令都要经过 MEM

指令类型用数据存储器吗MEM 阶段在做什么
LW✓ 读把数据读进 LMD
SW✓ 写把寄存器的值写进存储器
BEQZ / BNEZ✗完成分支:按 Cond 更新 PC
ADD / SUB / AND …✗空操作,结果直接穿过

所以第 4 步全名叫"Memory Access / Branch Completion"。DLX 流水线是固定五级结构,每条指令都占用这一级,即使什么都不做。

11.4 非流水 DLX 的 CPI

非流水实现中,每一步用一个时钟周期,用 NPC、IR、A、B、Imm、Cond、ALUOut、LMD 这些临时寄存器保存步与步之间的中间结果。不需要 WB 的指令可以少一个周期:

分支 4 周期(不写回) store 4 周期(不写寄存器) 其余 5 周期 讲义:分支 12%、store 5%、其他 83% CPI = 4 × 0.12 + 4 × 0.05 + 5 × 0.83 = 4.83

11.5 流水线寄存器 F/D · D/A · A/M · M/W

流水化以后,原来的临时寄存器变成四组级间缓冲(inter-stage buffer),用边沿触发寄存器实现:时钟沿到来时锁存输入,保持到下一个时钟沿。这样每一级在一个周期结束时把结果交给下一级,多条指令才能同时在流水线里走而互不干扰。讲义类比的器件是 74LS374 边沿触发 D 锁存器。

缓冲位置存什么(讲义)
F/DIF → IDIR(指令本身)、NPC
D/AID → EXA、B、Imm、b(目的寄存器号)、OP(操作码)、Cond、NPC
A/MEX → MEMALUOut(运算结果或有效地址)、NPC、Cond、b、OP;store 还要带上 B(要写的数据,图上叫 SMD)
M/WMEM → WBLMD(load 读到的数据)、ALUOut、b、OP
填表口诀

每个缓冲存的是"下一级要用、而且后面还要接着用"的东西。目的寄存器号 b 和 OP 要一路带到 WB,因为写回时才用得上。以 LW R1, 20(R2)、R2 = 100、Mem[120] = 55 为例(Tutorial 11.7):F/D = {IR = LW R1,20(R2), NPC};D/A = {A = 100, Imm = 20, b = R1, OP = LW};A/M = {ALUOut = 120, b = R1, OP};M/W = {LMD = 55, b = R1, OP}。

课堂练习 · In-class exercise
Exercise 11.3LW 的流水线寄存器 · Pipeline registers for LW
EN

For LW R1, 30(R2), identify the information that must be stored in each inter-stage register (F/D, D/A, A/M, M/W) so that the next stage can continue.

中

对 LW R1, 30(R2),写出每个级间寄存器(F/D、D/A、A/M、M/W)必须保存的信息,使下一级能继续执行。

解答 · Solution
Register 寄存器Contents 内容
F/DIR (the instruction 指令本身), NPC
D/AA = Regs[R2], Imm = 30, b = R1 (destination 目的号), OP = LW (also B, NPC)
A/MALUOut = R2 + 30 (effective address 有效地址), b, OP
M/WLMD = Mem[R2 + 30], b, OP (ALUOut)
EN

Each buffer keeps what the next stage needs plus what later stages still need. The destination register number b and OP travel all the way to WB, where the write happens.

中

每个缓冲存"下一级要用、后面还要接着用"的东西。目的寄存器号 b 和 OP 一路带到 WB,因为写回时才用。

交互实验 06
DLX 数据通路步进器

选一条指令和初始值,按"下一步"走过 IF → ID → EX → MEM → WB。图中高亮的是这一拍用到的部件和连线,下方表格逐行填出 Tutorial 11 要求的"操作 / 关键输出 / 是否访问数据存储器",以及四个流水线寄存器的内容。

尚未开始
PC Add+4 Instr.Memory RegFile Sign Ext16 → 32 Zero? ALU DataMemory NPC IR A B Imm b(目的号)· OP ALUOut Cond B → SMD LMD 分支目标 ALUOut → PC MUX 写回数据 → Reg File F/D D/A A/M M/W IF ID EX MEM WB
阶段执行的操作关键输出数据存储器

11.6 流水线性能与要点

理想情况下五级流水线每个周期完成一条指令,CPI = 1。讲义例子:非流水 DLX 时钟 1 ns,ALU 指令 4 周期(40%)、分支 4 周期(20%)、访存指令 5 周期(40%);流水化额外增加 0.1 ns 时钟开销(寄存器延迟、时钟偏移)。

非流水 CPI = 0.4 × 4 + 0.2 × 4 + 0.4 × 5 = 4.4 → 每条 4.4 × 1 ns = 4.4 ns 流水:CPI ≈ 1,时钟 1.0 + 0.1 = 1.1 ns → 每条 1.1 ns 加速比 = 4.4 / 1.1 = 4 通式:平均每条指令时间 = CPI × 时钟周期;加速比 = 非流水时间 / 流水时间
  • 流水线提高的是吞吐量,不减少单条指令的延迟。
  • 单端口存储器里,取指(IF)和访存(MEM)要同时用存储器,不同指令会抢同一资源,这就是结构冒险,需要停顿。
  • 实际流水线因为冒险,CPI > 1。三类冒险是 Lesson 12 的内容。
课堂练习 · In-class exercises
Exercise 11.6三条指令的 A、B、Imm 与 ALUOut · A, B, Imm and ALUOut for three instructions
EN

R1 = 10, R2 = 100, R3 = 20, Mem[104] = 50. Sequence: LW R4, 4(R2); ADDI R5, R4, #10; ADD R6, R5, R1. For each: the values of A, B (if used) and Imm, ALUOut, the value written back, and the datapath components used.

中

R1 = 10、R2 = 100、R3 = 20,Mem[104] = 50。序列:LW R4, 4(R2);ADDI R5, R4, #10;ADD R6, R5, R1。对每条写出 A、B(若用到)、Imm、ALUOut、写回值和用到的部件。

解答 · Solution
Instruction 指令A / B / ImmALUOutWrite-back 写回Components 部件
LW R4,4(R2)A = 100, Imm = 4104R4 ← 50IM, RegFile, SignExt, ALU, DM, LMD, WB MUX
ADDI R5,R4,#10A = 50, Imm = 1060R5 ← 60IM, RegFile, SignExt, ALU (MUX → Imm), WB
ADD R6,R5,R1A = 60, B = 1070R6 ← 70IM, RegFile, ALU (MUX → B), WB
EN

Values are computed as if the instructions run one after another. In a real pipeline LW → ADDI is a load-use hazard and ADDI → ADD is a RAW hazard, handled by forwarding and stalls (Lesson 12).

中

这里按顺序执行、不考虑冒险来算数值。真正流水起来,LW → ADDI 是 load-use,ADDI → ADD 也是 RAW,要靠转发和停顿(Lesson 12)。

Exercise 11.7四条指令的时序图 · Timing diagram for four instructions
EN

Draw a timing diagram for LW R1, 0(R2); ADD R3, R4, R5; ADDI R6, R7, #8; BEQZ R8, LOOP, showing the stage of each instruction in every clock cycle. Assume ideal pipelining with no stalls.

中

画出 LW R1, 0(R2);ADD R3, R4, R5;ADDI R6, R7, #8;BEQZ R8, LOOP 的时序图,标出每个周期每条指令所在的级。假设理想流水、无停顿。

解答 · Solution
Cycle 周期I1 LWI2 ADDI3 ADDII4 BEQZ
1IF
2IDIF
3EXIDIF
4MEMEXIDIF
5WBMEMEXID
6WBMEMEX
7WBMEM
8WB
EN

Total 4 + 5 − 1 = 8 cycles. The four instructions share no registers, so ideal pipelining holds.

中

共 4 + 5 − 1 = 8 个周期。四条指令之间没有寄存器依赖,所以理想流水成立。

Exercise 11.8LW、ADDI、BEQZ 的 ALU 与结果 · ALU values and results for LW, ADDI, BEQZ
EN

R2 = 1000, Mem[1030] = 300, R3 = 25, PC = 4000. Sequence: LW R1, 30(R2); ADDI R4, R1, #5; BEQZ R3, +20. For each: the values entering the ALU, ALUOut, whether data memory is accessed, the value written back (if any), and the final result after WB.

中

R2 = 1000,Mem[1030] = 300,R3 = 25,PC = 4000。序列:LW R1, 30(R2);ADDI R4, R1, #5;BEQZ R3, +20。对每条写出 ALU 输入、ALUOut、是否访问数据存储器、写回值(若有)和最终结果。

解答 · Solution
Instruction 指令ALU inputs ALU 输入ALUOutData mem 数据存储器Result 结果
LW R1,30(R2) @4000A = 1000, Imm = 301030Read 读R1 ← 300
ADDI R4,R1,#5 @4004A = 300, Imm = 5305No 否R4 ← 305
BEQZ R3,+20 @4008NPC = 4012, Imm = 204032No 否R3 = 25 ≠ 0 → not taken 不跳, PC = 4012
EN

BEQZ sits at 4008, so NPC = 4012 and the target is 4012 + 20 = 4032; R3 = 25 is not zero, so the branch is not taken and no register is written.

中

BEQZ 位于 4008,NPC = 4012,目标 4012 + 20 = 4032;R3 = 25 不为 0,所以不跳转,也不写任何寄存器。

自测 · Self-Check

ADD R7, R3, R4,R3 = 25,R4 = 17,PC = 200。每一级的关键输出是什么?
IF:IR = ADD R7,R3,R4,NPC = 204。ID:A = 25,B = 17,b = R7。EX:ALUOut = 42。MEM:不访存,ALUOut 直接传下去。WB:R7 ← 42。只有 IF 访问了(指令)存储器,数据存储器全程未用(Tutorial 11.1)。
为什么 ID 阶段对 R 型指令也做符号扩展?会出错吗?
ID 阶段的译码、读寄存器和符号扩展是并行做的(固定字段译码),硬件不必等译码完成再决定做不做扩展,这样更快。R 型指令只是不用 Imm(EX 的 MUX 选 B),所以不会出错。
LW 和 ADD 在 EX 阶段,ALU 第二个输入的 MUX 分别选什么?
LW 选 Imm(算 A + Imm 得有效地址);ADD 选 B(算 A + B)。分支指令第一个输入选 NPC,第二个选 Imm(算目标地址)。
非流水实现里 CPI = 4.6、时钟 0.8 ns;流水线 CPI = 1.2、时钟 1.0 ns。加速比多少?
非流水 4.6 × 0.8 = 3.68 ns,流水 1.2 × 1.0 = 1.2 ns,加速比 3.68 / 1.2 ≈ 3.07(Tutorial 11.8)。
为什么 b(目的寄存器号)要一路存到 M/W,而不是在 ID 直接写好?
寄存器要到 WB 阶段才写,而这时 ID 里已经是后面第三条指令了,它的 IR 字段已经换成别的值。所以目的寄存器号必须跟着自己的指令在 D/A → A/M → M/W 里一级级往下传,WB 时才知道写哪个寄存器。
LESSON 12

流水线冒险

Final 范围

理想流水线 CPI = 1,加速比等于级数。现实里,下一条指令经常不能在它该进入的那个周期进入:要么硬件资源被占(结构冒险),要么要用的数据还没算出来(数据冒险),要么连下一条该取哪条都还不知道(控制冒险)。这一课逐个分析三类冒险,以及转发、互锁、编译器调度、分支预测、延迟槽这些对策,最后都落到一个公式上:CPI = 1 + 每条指令的平均停顿周期。

12.1 三类冒险与 CPI

冒险原因例子典型对策
结构冒险硬件资源冲突单端口存储器:取指和 load/store 同一周期都要用双端口存储器、分离的指令/数据缓存
数据冒险指令依赖前面指令的结果ADD R1,… 后紧跟 SUB …,R1,…转发、流水线互锁、编译器调度
控制冒险分支结果还不知道BEQZ R1, Label提前判断分支、分支预测、延迟槽
CPIpipeline = 1 + 每条指令的平均停顿周期 平均每条指令时间 = CPI × 时钟周期 加速比 = CPIunpipelined / CPIpipeline = 5 / (1 + 停顿) (5 级、各级等长、无流水开销时) 更一般:加速比 = 流水深度 / (1 + 停顿) × (非流水时钟 / 流水时钟)

例:10% 的指令停顿 1 周期。加权平均 0.9 × 1 + 0.1 × 2 = 1.1,或者 1 + 0.1 × 1 = 1.1,两种算法一样。加速比 5 / 1.1 = 4.54,比理想的 5 低。

课堂练习 · In-class exercises
Exercise 12.1判断冒险类型 · Identify the hazard type
EN

Identify the hazard type: (1) a load instruction and an instruction fetch both need the same memory in the same cycle; (2) an instruction uses a register value before a previous instruction writes it; (3) the processor does not yet know whether a branch will be taken.

中

判断冒险类型:(1) load 和取指在同一周期要用同一个存储器;(2) 一条指令在前一条写寄存器之前就读了它;(3) 处理器还不知道分支跳不跳。

解答 · Solution
EN

(1) Structural hazard (2) Data hazard (RAW) (3) Control hazard

中

(1) 结构冒险 (2) 数据冒险(RAW) (3) 控制冒险

Exercise 12.2停顿后的 CPI 与加速比 · CPI and speedup with stalls
EN

A pipelined processor has an ideal CPI of 1 and an average of 0.25 stall cycles per instruction. Compute (1) the pipeline CPI, (2) the speedup relative to an unpipelined processor with CPI = 5.

中

流水线理想 CPI 1,平均每条停顿 0.25 周期。求 (1) 流水线 CPI;(2) 相对 CPI = 5 的非流水处理器的加速比。

解答 · Solution
EN
(1) CPI = 1 + 0.25 = 1.25(2) Speedup = 5 / 1.25 = 4
中
(1) CPI = 1 + 0.25 = 1.25(2) 加速比 = 5 / 1.25 = 4

12.2 结构冒险

两条或更多指令在同一周期要用同一个硬件资源,资源又不能同时满足,就是结构冒险。例子:寄存器堆只有一个写端口;指令和数据共用一个单端口存储器。解决方法:停顿到资源空出来,或者复制资源(双端口存储器有两套独立的地址、数据和控制信号,一个端口给 IF,一个给 MEM,冲突就消失了)。

讲义例子:LOAD 在第 4 周期处于 MEM,此时第 4 条指令正要 IF,两者都要访问存储器,只能让取指晚一拍。

40% 的指令是 load/store,每条造成 1 周期结构停顿 CPI = 1 + 0.4 × 1 = 1.4 即使时钟快 5%:平均指令时间 = 1.4 × Tideal / 1.05 = 1.33 Tideal

结论:消除结构冒险往往比小幅提高主频更划算。

课堂练习 · In-class exercises
Exercise 12.3单端口存储器会冲突吗 · Single-port memory conflict
EN

A processor uses a single-port memory. Can a structural hazard occur between LW R1,0(R2) and ADD R3,R4,R5 when LW is in the MEM stage and ADD is in the IF stage?

中

处理器使用单端口存储器。当 LW R1,0(R2) 处于 MEM 级、ADD R3,R4,R5 处于 IF 级时,会发生结构冒险吗?

解答 · Solution
EN

Yes. LW needs a data read and ADD needs an instruction fetch in the same cycle, but there is only one memory port, so the fetch must be delayed by one cycle (a stall). Separate instruction/data memories or a dual-port memory remove the hazard.

中

会。LW 要读数据、ADD 要取指,两者在同一周期都要用唯一的存储器端口,只能把取指推迟一拍(插入停顿)。用分离的指令/数据存储器或双端口存储器可以消除。

Exercise 12.4结构停顿的 CPI · CPI with structural stalls
EN

25% of instructions are loads/stores and each causes one structural stall. Find (1) the average stall cycles per instruction, (2) the CPI.

中

25% 的指令是 load/store,每条造成 1 个结构停顿。求 (1) 平均每条停顿;(2) CPI。

解答 · Solution
EN
(1) 0.25 × 1 = 0.25(2) CPI = 1 + 0.25 = 1.25
中
(1) 0.25 × 1 = 0.25(2) CPI = 1 + 0.25 = 1.25

12.3 数据冒险:RAW / WAR / WAW

流水线改变了对操作数的读写顺序,如果和顺序执行时的顺序不一致,指令就可能用到错误的值。设指令 i 在前、j 在后:

类型含义例子DLX 五级流水线会发生吗
RAW 写后读
真相关 true dependence
j 在 i 写之前读了寄存器LW R1,0(R2)
SUB R4,R1,R5
会。最常见,是 DLX 唯一要处理的数据冒险
WAR 读后写
反相关 anti-dependence
j 在 i 读之前写了寄存器ADD R1,R2,R3(读 R2)
LW R2,0(R5)(写 R2)
不会:所有读都在 ID(第 2 级),所有写都在 WB(第 5 级),读永远早于后面指令的写
WAW 写后写
输出相关 output dependence
j 比 i 先写同一寄存器,最终值错了LW R1,0(R2)
ADD R1,R3,R4
不会:所有指令都在 WB 写、长度相同,按程序顺序到达 WB

WAR 和 WAW 在指令延迟不等、乱序执行、超标量处理器里才会出现(例如 ADD 比 LW 先到写回级,LW 后写反而覆盖了 ADD 的结果)。讲义把它们列出来是为了完整。

判别步骤(Tutorial 12.5)

看两条指令的寄存器:前一条的目的 = 后一条的源 → RAW;前一条的源 = 后一条的目的 → WAR;两条的目的相同 → WAW;都不相交 → 无。注意 LW 的目的是 Rt,SW 没有目的寄存器(它的两个寄存器都是源)。

课堂练习 · In-class exercises
Exercise 12.7分类依赖 · Classify each dependency
EN

Classify each dependency: (1) ADD R1,R2,R3 / SUB R4,R1,R5; (2) ADD R1,R2,R3 / LW R2,0(R5); (3) LW R1,0(R2) / ADD R1,R3,R4.

中

分类:(1) ADD R1,R2,R3 / SUB R4,R1,R5;(2) ADD R1,R2,R3 / LW R2,0(R5);(3) LW R1,0(R2) / ADD R1,R3,R4。

解答 · Solution
EN

(1) RAW (ADD writes R1, SUB reads it) (2) WAR (ADD reads R2, LW writes it) (3) WAW (both write R1)

中

(1) RAW(ADD 写 R1,SUB 读 R1) (2) WAR(ADD 读 R2,LW 写 R2) (3) WAW(两条都写 R1)

Exercise 12.8DLX 里会出现哪些数据冒险 · Which data hazards occur in DLX
EN

Which of RAW, WAR and WAW can occur in a basic DLX 5-stage pipeline? Explain briefly.

中

RAW、WAR、WAW 中哪些会出现在基本 DLX 五级流水线里?简要说明。

解答 · Solution
EN

Only RAW. WAR cannot occur: all reads happen in ID (stage 2) and all writes in WB (stage 5), so an earlier instruction always reads first. WAW cannot occur: all instructions have the same length and write in WB, so writes complete in program order.

中

只有 RAW。WAR 不会:所有读都在 ID(第 2 级),所有写都在 WB(第 5 级),前一条一定先读完。WAW 不会:所有指令长度相同、都在 WB 写,写回顺序与程序顺序一致。

12.4 寄存器分半周期与转发

序列 ADD R1,R2,R3 后面跟着 SUB R4,R1,R5、AND R6,R1,R7、OR R8,R1,R9、XOR R10,R1,R11。ADD 在第 5 周期(WB)才写 R1:

  • 寄存器堆分半周期:一个周期内前半写、后半读。所以和 WB 同一周期的 ID 能读到新值。
  • SUB 在第 3 周期读 R1、AND 在第 4 周期读 → 都读到旧值,有冒险。
  • OR 在第 5 周期的后半读,正好拿到 ADD 前半写进去的新值 → 无冒险。XOR 更晚,也没问题。
  • 结论:没有转发时,只有紧跟其后的两条指令需要停顿。

转发(forwarding,也叫旁路 bypassing):结果其实在 EX 结束时就已经算出来了,没必要等写回寄存器堆。把 EX/MEM(A/M)和 MEM/WB(M/W)流水线寄存器里的值直接送回 ALU 输入端:

  • 硬件:在 ALU 每个输入前加一个 MUX,加上从 EX/MEM、MEM/WB 回来的通路,再加冒险检测逻辑来选输入。
  • 正常情况选寄存器堆读出的值;检测到冒险就选转发来的值。
  • ADD → SUB、SUB → AND 这类 ALU 到 ALU 的 RAW 全部可以零停顿解决。紧挨着的那条从 EX/MEM 拿值(Exercise 12.10 选 B)。
课堂练习 · In-class exercises
Exercise 12.5哪些指令有 RAW · Which instructions have RAW hazards
EN

Determine which instructions have RAW hazards: ADD R1,R2,R3; SUB R4,R1,R5; AND R6,R1,R7; OR R8,R9,R10.

中

判断哪些指令有 RAW 冒险:ADD R1,R2,R3;SUB R4,R1,R5;AND R6,R1,R7;OR R8,R9,R10。

解答 · Solution
EN

SUB and AND: both read R1, which ADD writes, before ADD has written it back. OR does not use R1, so it has no hazard.

中

SUB 和 AND:都读 ADD 写的 R1,而且在 ADD 写回之前读。OR 不用 R1,没有冒险。

Exercise 12.6同一周期能安全读谁 · Who can safely read in the write cycle
EN

Assume the register write occurs in the first half of a cycle and the read in the second half. ADD writes R1 in CC5. Which instruction can safely read R1 in CC5?

中

假设寄存器前半周期写、后半周期读。ADD 在 CC5 写 R1。哪条指令可以在 CC5 安全地读 R1?

解答 · Solution
EN

The instruction that is in ID during CC5, i.e. the third instruction after ADD (OR R8,R1,R9 in the slide example): ADD writes in the first half of the cycle and OR reads the new value in the second half.

中

在 CC5 处于 ID 级的那条,也就是 ADD 之后的第三条指令(讲义例子里的 OR R8,R1,R9):ADD 前半周期写入,它后半周期读出,拿到的是新值。

Exercise 12.9转发能否消除冒险 · Does forwarding remove the hazard
EN

Determine whether forwarding eliminates the hazard: (1) ADD R1,R2,R3 / SUB R4,R1,R5; (2) SUB R1,R2,R3 / AND R4,R1,R5.

中

判断转发能否消除冒险:(1) ADD R1,R2,R3 / SUB R4,R1,R5;(2) SUB R1,R2,R3 / AND R4,R1,R5。

解答 · Solution
EN

Yes for both. Each is an ALU result used by the next ALU instruction, forwarded from EX/MEM to the ALU input with zero stalls.

中

两个都能。都是 ALU 结果给紧跟的 ALU 指令用,从 EX/MEM 转发到 ALU 输入,零停顿。

Exercise 12.10转发值来自哪里 · Where the forwarded value comes from
EN

ADD R1,R2,R3 followed by SUB R4,R1,R5 requires forwarding. Which pipeline register provides the forwarded value? A. IF/ID B. EX/MEM C. Register file

中

ADD R1,R2,R3 后紧跟 SUB R4,R1,R5,需要转发。转发值来自哪个流水线寄存器?A. IF/ID B. EX/MEM C. 寄存器堆

解答 · Solution
EN

B. EX/MEM. When SUB is in EX, ADD is in MEM and its result sits in the EX/MEM pipeline register (A/M in the slides).

中

B. EX/MEM。SUB 处于 EX 时,ADD 正好在 MEM,结果存在 EX/MEM(讲义叫 A/M)流水线寄存器里。

12.5 Load-use 冒险与硬件互锁

转发不能解决所有 RAW。LW R1,0(R2) 的数据要到 MEM 结束才从存储器读出来,而紧跟的 SUB R4,R1,R6 在下一个周期的 EX 开始就要用,时间上往回倒了,转发也来不及。

情形需要停顿
ALU → ALU(ADD → SUB、SUB → AND),有转发0
LW → 紧跟的使用者,有转发1(load delay = 1)
LW → 紧跟的使用者,无转发2(要等 WB)
ALU → 紧跟的使用者,无转发2

硬件流水线互锁(interlock)检测到这种冒险后自动停顿流水线,插入一个气泡(NOP),依赖指令和后面所有指令都推迟一拍,正确性由硬件保证,不需要软件介入:

指令123456789
LW R1,0(R2)IFIDEXMEMWB
SUB R4,R1,R6IFIDstallEXMEMWB
AND R6,R1,R7IFstallIDEXMEMWB
OR R8,R1,R9stallIFIDEXMEMWB
课堂练习 · In-class exercises
Exercise 12.11load-use 要不要停顿 · Does load-use need a stall
EN

Determine whether a stall is needed for LW R1,0(R2) followed by SUB R4,R1,R5, assuming forwarding is available.

中

有转发时,LW R1,0(R2) 后紧跟 SUB R4,R1,R5,需要停顿吗?

解答 · Solution
EN

Yes, one cycle. The loaded data is only available at the end of MEM, but SUB needs it at the start of its EX in that same cycle, so even forwarding is too late; after a one-cycle stall it is forwarded from MEM/WB.

中

需要,1 拍。load 的数据在 MEM 结束才从存储器读出,紧跟的 SUB 在同一周期的 EX 开始就要用,转发也来不及;停 1 拍后从 MEM/WB 转发。

Exercise 12.12load-use 的 CPI · CPI from load-use stalls
EN

20% of instructions are loads, half of them are immediately followed by a dependent instruction, and each load-use hazard causes one stall. Find the CPI.

中

20% 的指令是 load,其中一半紧跟着依赖它的指令,每次 load-use 停 1 拍。求 CPI。

解答 · Solution
EN
Stalls/instruction = 0.2 × 0.5 × 1 = 0.1CPI = 1 + 0.1 = 1.1
中
停顿/条 = 0.2 × 0.5 × 1 = 0.1CPI = 1 + 0.1 = 1.1

12.6 编译器调度消除停顿

与其让硬件停顿,不如让编译器把不相关的指令挪进停顿的位置,让 CPU 在等数据的那一拍干点有用的活。只要被移动的指令和周围没有依赖,程序结果就不变。

a = b + c; d = e − f;(变量都在内存里,有转发)
原始代码调度后
LW Rb,b
LW Rc,c
ADD Ra,Rb,Rc ← 停 1 拍
SW a,Ra
LW Re,e
LW Rf,f
SUB Rd,Re,Rf ← 停 1 拍
SW d,Rd
LW Rb,b
LW Rc,c
LW Re,e ← 提前,填掉空拍
ADD Ra,Rb,Rc
LW Rf,f
SW a,Ra
SUB Rd,Re,Rf
SW d,Rd

调度后 ADD 距离 LW Rc 隔了一条,SUB 距离 LW Rf 也隔了一条,两次 load-use 停顿都消失了,不需要额外硬件。

交互实验 07
流水线时序图生成器

输入一段 DLX 代码(每行一条,支持 ADD/SUB/AND/OR/XOR/SLT、ADDI/SUBI/ANDI/ORI、LW、SW、BEQZ/BNEZ、J、NOP,标号写在行首加冒号),选择是否转发、分支在哪一级判定和用什么方案,自动画出每条指令每个周期所处的阶段,并列出所有冒险和停顿数。

    模型说明:寄存器堆前半周期写、后半周期读;有转发时 ALU 结果在下一周期的 EX 可用,load 结果在 MEM 之后的下一周期 EX 可用(SW 要存的数据在 MEM 才用);无转发时使用者必须在生产者 WB 的同一周期或之后才能读寄存器。分支的控制代价按讲义口径单独计算(EX 判定代价 3、ID 判定代价 1),分支比较用到的寄存器按普通 ALU 操作数处理。时序表只画实际执行路径上的指令,跳转后被冲掉的错误路径指令不画出来,用 bubble 表示损失的周期。

    12.7 控制冒险

    原始 DLX 在 EX 级才算出分支目标和判断条件。等结果出来时,后面已经有三条顺序指令进了流水线;如果分支跳转,这些指令都不该执行。最简单的办法是停顿:

    原始 DLX:分支代价 3 周期 CPI = 1 + 0.3 × 3 = 1.9(分支占 30%) 改进 DLX:在 ID 级加一个比较器判断条件、一个加法器算目标地址      分支代价降为 1 周期 CPI = 1 + 0.3 × 1 = 1.3
    为什么是 3 不是 2

    严格说,EX 结束时结果就知道了,下一拍就能取目标,代价是 2。讲义按经典 DLX 的做法,在 MEM 级才把新 PC 写回,所以记为 3 周期。考试按讲义:EX 判定 = 3,ID 判定 = 1。题目给定了代价(如 Tutorial 12.8 的"错误预测代价 3")就直接用给定值。

    实测统计:条件分支约占指令的 14–16%;向前分支约 60% 跳转,向后分支(循环)约 85% 跳转,整体约 67% 跳转。分支又多又常跳,所以值得做预测。

    课堂练习 · In-class exercises
    Exercise 12.13分支停顿的 CPI · CPI with branch stalls
    EN

    Branch frequency = 30%, branch penalty = 3 cycles. Find the CPI.

    中

    分支占 30%,分支代价 3 周期。求 CPI。

    解答 · Solution
    EN
    CPI = 1 + 0.3 × 3 = 1.9
    中
    CPI = 1 + 0.3 × 3 = 1.9
    Exercise 12.14改进 DLX 的 CPI · CPI of the enhanced DLX
    EN

    The enhanced DLX reduces the branch penalty from 3 cycles to 1 cycle. With a branch frequency of 30%, compute the new CPI.

    中

    改进 DLX 把分支代价从 3 周期降到 1 周期。分支占 30%,求新的 CPI。

    解答 · Solution
    EN
    CPI = 1 + 0.3 × 1 = 1.3 (down from 1.9)
    中
    CPI = 1 + 0.3 × 1 = 1.3(原来 1.9)

    12.8 四种分支处理方案

    讲义口径:改进 DLX,分支占 30%,跳转 53% / 不跳 47%,代价 1
    方案做法每个分支的平均代价CPI
    1 停顿知道结果前不取新指令总是 11.30
    2 预测不跳转继续取 PC + 4;猜错(实际跳转)就冲掉取错的那条,从目标重新取0.47 × 0 + 0.53 × 1 = 0.531.159
    3 预测跳转从目标地址取;但 DLX 在 ID 结束才知道目标,猜对也要等一拍0.53 × 1 + 0.47 × 1 = 11.30
    4 延迟分支分支后面那一条(延迟槽)无论跳不跳都执行,由编译器放一条有用的指令进去60% 能填 × 80% 有用 = 48% 无代价,0.52 × 11.156

    填延迟槽的三种来源

    来源做法安全性适用
    From before把分支前面一条无关指令挪进槽里(分支条件不能依赖它)高,本来就要执行首选
    From target把跳转目标处的第一条指令复制进槽里中:不跳转时它也会执行常跳的分支,比如循环末尾的向后分支
    From fall-through把不跳转时会执行的下一条挪进槽里中:跳转时它也会执行常不跳的分支,比如 if 语句的向前分支

    Tutorial 12.3 是一个带修改的 from-before 例子:循环里把 SW 200(R3),R2 挪到 BNEZ 后面的槽里,但它前面的 SUBI R3,R3,#4 已经把 R3 减了 4,所以偏移要改成 SW 204(R3),R2 才能存到原来的地址。

    可取消分支(canceling / nullifying branch):编译器同时给出预测方向,预测对了槽里的指令正常执行,预测错了就把它当 NOP 作废,不会产生错误结果。延迟槽的问题是合适的指令常常找不到、from target / fall-through 不一定安全、流水线一深就要填好几个槽,所以现代 CPU 改用硬件动态分支预测:猜对零代价,猜错冲刷流水线重来。

    做法谁来解决性能硬件复杂度
    NOP 延迟槽没人差(每个分支浪费 1 拍)很低
    填充延迟槽编译器较好(常为 0 代价)低
    分支预测硬件最好高
    课堂练习 · In-class exercises
    Exercise 12.15预测不跳转的 CPI · CPI with predict-not-taken
    EN

    Branch frequency = 30%, predict-not-taken accuracy = 47%. Compute the CPI (misprediction penalty = 1 cycle).

    中

    分支占 30%,预测不跳转的准确率 47%。求 CPI(猜错代价 1 周期)。

    解答 · Solution
    EN
    Mispredicted (actually taken) = 53%CPI = 1 + 0.3 × 0.53 × 1 = 1.159
    中
    猜错(实际跳转)比例 = 53%CPI = 1 + 0.3 × 0.53 × 1 = 1.159
    Exercise 12.16哪条能放进延迟槽 · Filling the delay slot
    EN

    Code: ADD R10,R11,R12; BEQZ R1,Label; NOP; SUB R4,R5,R6. Which instruction could be moved into the delay slot? A. ADD R10,R11,R12 B. SUB R4,R5,R6

    中

    代码:ADD R10,R11,R12;BEQZ R1,Label;NOP;SUB R4,R5,R6。哪条可以挪进延迟槽?A. ADD R10,R11,R12 B. SUB R4,R5,R6

    解答 · Solution
    EN

    A (from before): ADD executes anyway and does not affect the branch condition R1, so it is correct whether or not the branch is taken. SUB belongs to the fall-through path; in the slot it would also run when the branch is taken, which is unsafe.

    中

    A(from before):ADD 本来就要执行,又与分支条件 R1 无关,放进槽里无论跳不跳都正确。SUB 只在不跳转时执行,挪进槽里会在跳转时也被执行,不安全。

    Exercise 12.17综合:三类冒险与停顿 · All three hazard types together
    EN

    Consider LW R1,0(R2); ADD R3,R1,R4; BEQZ R3,Label; SUB R5,R6,R7. Identify (1) structural hazards, (2) data hazards, (3) control hazards, (4) the number of stall cycles assuming forwarding and a 1-cycle branch penalty.

    中

    代码:LW R1,0(R2);ADD R3,R1,R4;BEQZ R3,Label;SUB R5,R6,R7。找出 (1) 结构冒险;(2) 数据冒险;(3) 控制冒险;(4) 有转发、分支代价 1 拍时的停顿周期数。

    解答 · Solution
    EN

    (1) None if instruction and data memories are separate; with a single-port memory, LW's MEM would clash with a later IF. (2) LW → ADD (R1, load-use, 1 stall); ADD → BEQZ (R3, resolved by forwarding in the slides' model). (3) SUB after BEQZ must wait for the branch outcome: 1-cycle penalty. (4) 2 stall cycles in total.

    Note: if the branch really compares in ID (enhanced DLX), R3 is needed in ID but ADD produces it only at the end of EX, adding one more stall (3 in total). The slides do not discuss this; answer 2.

    中

    (1) 指令与数据存储器分离时没有;若是单端口存储器,LW 的 MEM 会和后面某条的 IF 冲突。(2) LW → ADD(R1,load-use,停 1 拍);ADD → BEQZ(R3,按讲义口径靠转发解决)。(3) BEQZ 之后的 SUB 要等分支结果,代价 1 拍。(4) 共 2 个停顿周期。

    补充:如果分支真在 ID 级比较(改进 DLX),R3 必须在 ID 就拿到,而 ADD 的结果在 EX 结束才有,还要再停 1 拍,共 3 拍。讲义没有讨论这一点,按 2 拍答。

    交互实验 08
    分支方案 CPI 计算器
    方案每个分支平均代价CPI加速比(对 5 级理想 = 5)

    自测 · Self-Check

    ADD R1,R2,R3 后面是 LW R2,0(R5)。这是什么冒险?会在 DLX 里出问题吗?
    WAR(ADD 读 R2,LW 写 R2)。在 DLX 五级流水线里不会出问题:ADD 在 ID(第 2 级)读 R2,LW 要到 WB(第 5 级)才写,读一定在写之前。
    有转发时,LW R1,0(R2) 紧跟 ADD R3,R1,R4,需要停几拍?为什么转发救不了?
    1 拍。LW 的数据在 MEM 结束才读出,ADD 在 LW 的 MEM 那一拍就处于 EX 并需要数据,转发只能把数据往"后"送,不能送回同一拍的开头。停 1 拍后,数据从 MEM/WB 转发给 ADD 的 EX。
    为什么 WAW 在 DLX 五级流水线里不会发生?
    所有指令长度一样(都是 5 级),而且都只在 WB 写寄存器,所以写回顺序和程序顺序一致,后面的指令不可能比前面的先写。
    15% 是分支,60% 跳转,预测不跳转,猜错代价 3。CPI 多少?和每个分支都停 3 拍比快多少?
    平均代价 0.6 × 3 = 1.8,附加 CPI 0.15 × 1.8 = 0.27,CPI = 1.27。全停顿:1 + 0.15 × 3 = 1.45。1.45 / 1.27 ≈ 1.14 倍(Tutorial 12.8)。
    DLX 里"预测跳转"为什么没有比"停顿"好?
    因为 DLX 的分支目标地址要到 ID 结束才算出来,和分支条件同时知道。就算猜"跳"猜对了,也得等目标地址出来才能取指,代价仍然是 1 拍;猜错也是 1 拍。所以平均代价还是 1,CPI 和停顿一样(1.3)。目标地址比条件更早知道的机器上,预测跳转才有优势。
    走完 Part 2

    Part 2 的两条主线其实是同一个思路:CPU 太快了,别让它等。存储层次用局部性把"等内存"压到 AMAT 和停顿周期里;流水线用时间并行把"等上一条指令"压到 CPI 接近 1,再用转发、互锁、调度、预测把冒险造成的停顿一点点抠回来。考试题几乎都能写成两个公式之一:AMAT = HT + MR × MP 和 CPI = 理想 CPI + 平均停顿。回到侧边栏勾选已经掌握的课,然后去习题小程序把 Tutorial 7–12 做一遍。