精简指令系统计算机(RISC)
RISC 的口号是”精简指令”,但它精简的不是指令的条数,而是每条指令的功能。这个区别是本节一切结论的起点:
RISC 的核心主张:只保留最常用的简单指令,让每条指令都能在一个流水段内完成同样的事;复杂功能交给编译器用多条简单指令合成。
换句话说,它把 CISC 花在”让硬件理解复杂语义”上的预算,全部挪去买流水线、寄存器和时钟频率。 4.4.2 要讲清的就是:这笔钱为什么值得挪。
机制
典型特征
| 维度 | RISC 的做法 | 换来什么 |
|---|---|---|
| 指令长度 | 定长 | PC 增量恒定、取指与译码可并行 |
| 指令格式 | 少数几种,字段位置固定 | 译码一级完成,可提前读寄存器 |
| 寻址方式 | 少(常只有 3~5 种) | 有效地址计算路径单一且短 |
| 访存 | 只有 load / store | 访存只在固定的一个流水段发生 |
| 指令周期 | 等长,流水下 CPI 接近 1 | 流水段可以整齐切分 |
| 通用寄存器 | 多(≥32 个) | 减少访存次数 |
| 控制器 | 典型搭配硬布线 | 译码延迟最小 |
| 优化重心 | 编译器(寄存器分配、指令调度) | 硬件保持简单 |
这张表的每一行,右边那一列都指向同一个词:流水线。 RISC 的全部设计选择都是为了让流水线能跑满,这是理解它的唯一钥匙。
Load/Store 结构:为什么它是 RISC 最核心的一条
规定”只有 load/store 能访存”,看上去只是个限制,实际上它决定了流水线能不能整齐切分。
看两种做法在流水线上的差别。假设五段流水:取指 IF → 译码 ID → 执行 EX → 访存 MEM → 写回 WB。
RISC 的 add r1, r2, r3:
IF → ID → EX → (MEM 空过) → WB
CISC 的 add [ebx+8], eax(把主存单元加上 eax 再写回主存):
IF → ID → 算地址 → 读主存 → 加法 → 写主存
问题不在于它慢,而在于它的形状和别的指令不一样:
| RISC | CISC | |
|---|---|---|
| 每条指令的访存次数 | 0 次或 1 次,且位置固定 | 0、1、2 次都有可能 |
| 每条指令经过的阶段 | 完全相同 | 各不相同 |
| 流水段能否固定划分 | ✅ | ❌ 有的指令要在流水线中段访存两次 |
流水线的前提是”每条指令经过同样多、同样长的阶段”。 一旦有指令需要在中途访存两次,要么整条流水线为它加段(所有指令都变慢),要么让它阻塞流水线(5.5(本地资料) 的结构冒险)。Load/Store 结构就是从 ISA 层面直接禁止这种情况发生。
所以这条规定的真正含义是:把”访存”这件耗时且长度不定的事,隔离到专门的两条指令里,其余所有指令的行为都变得整齐可预测。
定长指令:把好处兑现到取指段
4.1.3 列过三条收益,在流水线上逐条兑现:
| 定长带来的 | 在流水线上是什么 |
|---|---|
| PC 增量是常数 | 取指段不必等译码结果,可以一直往前预取 |
| 操作码位置固定 | 译码是一级组合逻辑,ID 段延迟固定 |
| 寄存器字段位置固定 | ID 段可以在译码没出结果时就先读寄存器(读错了不用即可)→ 译码和取操作数合成一拍 |
第三条最值钱,它直接把流水线少掉一整段。这就是 4.1.4 那条主线的终点——变长指令省下的存储空间,在这里以流水段的形式加倍还了回去。
寄存器多:把访存次数按下去
RISC 通常有 32 个以上通用寄存器,这不是奢侈,是 Load/Store 结构的必要配套:既然只有 load/store 能访存,那中间结果就必须放得下。
回到 4.1.2 的访存次数表:操作数在寄存器里,那一次访存直接消失。 一段计算密集的代码,寄存器够多就能整段只在头尾各访存一次。在 3.1.2 那条”延迟差几个数量级”的背景下,这是最直接的性能来源。
寄存器多还有一个副作用:寄存器编号字段要变长(32 个寄存器要 5 位)。三地址指令就要 15 位放寄存器号——定长 32 位指令字正好装得下,这几个数字是互相配合设计出来的。
编译器接过了担子
RISC 把复杂度从硬件推给了编译器,所以 RISC 的成立前提是编译器足够好——这正是 CISC 那三条历史约束里消失得最彻底的一条。编译器要额外承担:
- 寄存器分配:把哪些变量常驻寄存器(图着色算法)
- 指令调度:重排指令顺序以填满流水线空档、避开数据相关(5.5(本地资料))
- 复杂操作的合成:一条 CISC 指令的活,拆成几条 RISC 指令并安排好顺序
“RISC 把优化重心从硬件移到编译器”这句话,具体就是指这三件事。
边界
RISC 精简的是”每条指令的功能”,不是”指令的条数”。 RISC-V 加上各种扩展后指令数量并不少,但它仍然是 RISC,因为每条指令依然简单、依然只有 load/store 访存。判断题若说”RISC 的指令条数一定比 CISC 少”,错。
“RISC 每条指令一个时钟周期”要按流水线口径理解。 一条指令从取指到写回仍要经过 5 个时钟周期(延迟),但流水线满载时平均每个周期完成一条(吞吐)。说的是 CPI 接近 1,不是延迟等于 1 个周期。 这正是 3.1.2 那条”延迟和吞吐是两个量”的主线在第 4 章的又一次出现。
“RISC 用硬布线控制器”是典型搭配,不是定义。 和 CISC 那条对称:定义在指令集,不在控制器。不过这个搭配的因果关系比 CISC 那边更紧——指令简单且格式统一,硬布线的组合逻辑才做得出来。
Load/Store 结构不等于”访存少”。 完成同样的工作,RISC 的访存数据量和 CISC 一样多(数据该读还得读),变的是访存被集中到了哪几条指令里。省下来的是那些本可以在寄存器里完成的中间结果的访存,这要靠寄存器多来兑现。
延迟槽是 MIPS 的历史包袱,不是 RISC 的通则。 早期 MIPS 让转移指令后面那条指令无论转不转都执行(把流水线气泡交给编译器填),RISC-V 和 ARM 都没有采用。统考若提到延迟槽,按题目说明处理即可。
RISC 也需要 Cache,而且更需要。 指令条数比 CISC 多、代码密度低,指令 Cache 的压力反而更大——这是 CISC 在 4.4.3 里唯一还站得住的一条优势。
对照速查
| 特征 | RISC | 换来什么 |
|---|---|---|
| 定长指令 | ✅ | 取指与译码可并行 |
| 格式统一、字段固定 | ✅ | ID 段可提前读寄存器 |
| 寻址方式少 | ✅ | 地址计算路径短 |
| 只有 load/store 访存 | ✅ | 流水段可整齐划分 |
| CPI ≈ 1(流水满载) | ✅ | 吞吐高 |
| 通用寄存器 ≥32 | ✅ | 减少访存 |
| 硬布线控制 | 典型搭配 | 译码延迟最小 |
| 编译器优化 | 重 | 硬件保持简单 |
| 判断 | 对错 |
|---|---|
| RISC 的指令条数一定少于 CISC | ❌ |
| RISC 精简的是单条指令的功能 | ✅ |
| RISC 每条指令的执行延迟是 1 个周期 | ❌(CPI≈1 是吞吐口径) |
| RISC 必须用硬布线控制器 | ❌(典型搭配) |
| Load/Store 结构使总访存数据量减少 | ❌(减少的是中间结果的访存) |
| RISC 代码密度比 CISC 高 | ❌(低) |
| 定长指令使 ID 段可以提前读寄存器 | ✅ |
| 延迟槽是所有 RISC 的共同特征 | ❌(MIPS 的历史选择) |
考点
- RISC 精简的是每条指令的功能,不是指令条数
- 特征:定长、格式统一、寻址方式少、Load/Store、CPI≈1、寄存器多、典型配硬布线、编译器优化重
- Load/Store 结构的意义是让每条指令经过相同的流水段,从 ISA 层禁止”中途访存两次”
- 定长指令的最大收益:ID 段可在译码出结果前先读寄存器,省掉一整段
- 寄存器多是 Load/Store 的必要配套,直接消掉访存次数表里的那几次
- CPI≈1 是吞吐口径,不是延迟口径
- 编译器接手三件事:寄存器分配、指令调度、复杂操作合成
- 硬布线控制是典型搭配不是定义;RISC 代码密度低
链接
- 🏠 返回总览:计算机组成原理第 4 章:指令系统总览
- ⬅️ 上一节:4.4.1 复杂指令系统计算机(CISC)
- ➡️ 下一节:4.4.3 CISC 和 RISC 的比较
- 🔗 4.1.3 定长操作码指令格式(定长的三条收益)
- 🔗 4.1.2 指令的基本格式(访存次数模板)
- 🔗 5.5 指令流水线(本地资料)(所有收益的兑现处)
- 🔗 3.1.2 存储器的性能指标(延迟与吞吐是两个量)
- 📖 名词库:第 4 章名词库