Appearance
CISC 与 RISC
概念
CISC(complex instruction set computer,复杂指令集计算机)与 RISC(reduced instruction set computer,精简指令集计算机)是指令系统设计上的两条路线:
- CISC:把功能做进指令里——一条指令尽量多干活,指令条数少但每条复杂。
- RISC:把功能还给编译器——指令只做最基本的事,靠多条简单指令组合出复杂功能。
打个比方:CISC 像一把瑞士军刀(一件工具集成二十种功能,单个功能用起来省事,但整把刀又重又贵);RISC 像一组标准螺丝刀(每把只做一件事,但每一把都便宜、好用、可以流水线生产)。
一句话记住分歧点:**CISC 省的是"程序的指令条数",RISC 省的是"每条指令的执行时间"。**两者都在追求"程序跑得快",只是选的杠杆不同。
原理
一、CISC 的由来与特点
CISC 的思路来自 1960–70 年代:
- 缩小"语义鸿沟":高级语言的语句与机器指令差得太远,编译器难写。那就把高级语言的操作直接做进指令(比如一条
CALL完成压栈+跳转+保存现场)。 - 微程序控制成熟:指令再复杂也不怕——用微程序(第 31 篇)实现就行,改指令只需改微程序,不用改硬件。
- 内存又慢又贵:程序越小越好,指令编码尽量紧凑(变长指令)。
代价是显而易见的:指令格式杂乱、译码复杂、执行周期长短不一(CPI 高)、难以流水线化。
二、RISC 的由来与五条设计原则
1970 年代末,实验数据给了 CISC 一记重击(见例 3):程序里 90% 的动态指令只用了 10% 的指令种类,剩下 90% 的指令种类几乎没人用,却占着译码器和微程序的复杂度。
于是 RISC 提出五条原则:
| # | 原则 | 理由 |
|---|---|---|
| 1 | 指令条数少且定长 | 定长 → 取指边界确定 → 流水线友好(第 32 篇) |
| 2 | Load-Store 结构 | 只有 load/store 能访存,运算指令只在寄存器间进行 → 便于统一流水线与产生单周期执行 |
| 3 | 寄存器数量多 | 减少访存次数(数据留在寄存器里) |
| 4 | 寻址方式少(2~3 种) | 地址计算简单、可并行、减少译码延迟 |
| 5 | 用硬布线控制器,不用微程序 | 指令简单到可以用门电路直接实现 → 更快 |
再加一条隐含原则:指令格式规整、字段位置固定,这样译码可以在取指的同时并行完成。
三、七项对比(必背表)
| 对比项 | CISC | RISC |
|---|---|---|
| 指令条数 | 多(通常 200~500 条) | 少(通常 < 200 条,甚至 < 100) |
| 指令字长 | 变长(1~15 字节) | 定长(如 MIPS32 恒 4 字节) |
| 指令格式 | 种类多、不规整 | 少而规整(MIPS 只有 R/I/J 三种) |
| 访存指令 | 任何指令都可以访存 | 只有 Load/Store 访存 |
| 寻址方式 | 多(十几种) | 少(2~3 种,MIPS 实质是"基址+偏移"一种) |
| 通用寄存器数 | 少(8~16 个) | 多(32~数百个) |
| CPI | 大(1~20,因指令而异) | 接近 1(多数指令单周期) |
| 控制器 | 多用微程序 | 多用硬布线 |
| 流水线 | 难(变长指令、执行时间不齐) | 容易(定长、单周期、Load-Store) |
| 编译难度 | 编译器能用的"高级原语"多 | 编译器要做更多优化(指令调度、寄存器分配) |
| 程序目标码大小 | 小(编码紧凑) | 大(定长 + 简单指令 → 条数多) |
| 功耗 / 面积 | 大 | 小 |
| 典型代表 | x86(Intel/AMD)、VAX、PDP-11 | MIPS、ARM、RISC-V、PowerPC、SPARC |
四、为什么"定长"这么重要
这是 RISC 与流水线的接口,也是 408 常从"流水线"角度反着考的点:
| 情形 | 取指阶段要做的事 | 后果 |
|---|---|---|
| 变长指令(CISC) | 先取若干字节,判断这条指令到底多长,才知道下一条从哪开始 | 取指时间不确定 → 流水线难以划分固定节拍 |
| 定长指令(RISC) | 直接取下一条(地址只要 +4) | 取指恒定时间 → 可以细分流水线 |
五、现代的现实:两者在融合
纯 CISC 与纯 RISC 的分界在今天已经模糊:
- x86 的外壳是 CISC,内核是 RISC:
add %eax, (%rbx)这样一条复杂指令,进入 CPU 后先被译码成若干条定长的微操作(micro-op, uop),后端流水线执行的其实是 RISC 风格的小操作。 - ARM / RISC-V 也在加指令:SIMD、加密、虚拟化扩展让 RISC 的指令数不断增加。
- 结论(考点的正确表述):CISC 与 RISC 的界线是设计哲学的界线,不是绝对的性能分界。今天的高性能 CPU 都是"前端接受复杂指令、后端按 RISC 流水线执行"。
六、目标码大小 vs 执行速度:一对真实的 trade-off
| 指标 | CISC | RISC |
|---|---|---|
| 完成同一任务的指令条数 | 少 | 多 |
| 每条指令的平均周期数 CPI | 高 | 低 |
| 总周期数 | 取决于两者乘积 | 取决于两者乘积 |
| 目标码体积 | 小 | 大 |
所以"谁更快"不是天生定的,要代进去算——这就是下面例 1、例 2 要干的事。
示例
例 1:指令条数与 CPI 的权衡
某高级语言程序编译后:在 CISC 机器上产生 100 条机器指令,平均 CPI = 4.0;在 RISC 机器上产生 300 条机器指令,平均 CPI = 1.2。两台机器主频相同。问哪台执行得快、快多少?
完整计算过程:
第一步,CISC 机器所需时钟周期数:
第二步,RISC 机器所需时钟周期数:
第三步,同主频下周期数之比就是时间之比:
结论:RISC 快,快约 11.1%。
这里的教学要点:CISC 的指令条数只有 RISC 的
决定速度的永远是
例 2:把主频加进来——结论会翻转
接着例 1。若 CISC 机器主频 2.0 GHz,RISC 机器主频 1.5 GHz(RISC 的简单设计通常能换来更高主频,但本例反过来给:假设 CISC 工艺更先进、主频更高)。求两者执行时间。
完整计算过程:
第一步,CISC 执行时间:
第二步,RISC 执行时间:
第三步,比较:
结论:主频一变,胜负翻转——CISC 反而快 20%。
这张表要记住(同一份程序、两种机器):
| 机器 | 指令条数 | CPI | 周期数 | 主频 | 执行时间 |
|---|---|---|---|---|---|
| CISC | 100 | 4.0 | 400 | 2.0 GHz | 200 ns |
| RISC | 300 | 1.2 | 360 | 1.5 GHz | 240 ns |
两句结论:
- 同主频时 RISC 略优(11.1%),主频差 33% 时 CISC 反超 20%。
- RISC"快"的前提是它能跑到更高的主频(结构简单、关键路径短、流水线级数可以更深)。脱离主频谈 CISC/RISC 谁快,是没有意义的。
例 3:80-20 法则的实测统计
RISC 运动的证据来自对真实程序的动态统计(下表中的比例是被广泛引用的经验数值,用于说明趋势而非精确复现某一台机器):
| 统计项 | 结果 | 含义 |
|---|---|---|
| 使用频率最高的前 10 条指令 | 约占动态指令数的 90% | 大量指令种类几乎从不执行 |
| 使用频率最高的前 20 条指令 | 约占 96% | 再往上加指令,收益极小 |
| 赋值 / 简单算术 / 条件转移 | 合计约占 60%~70% | 程序里绝大多数动作都很简单 |
| 过程调用与返回 | 约占 5%~10% | 但开销大,值得单独优化 |
| 复杂指令(如"字符串比较") | 极少使用 | 硬件实现了但没人用 |
结论:把 90% 的复杂度花在 10% 的使用频率上,是 CISC 的性价比错误。这就是 RISC 把"复杂的少数"交给编译器、用多条简单指令拼出来的理由。
同时也要看到反面:那 10% 的复杂指令在编译器做不好优化时确实能救急,而且目标码更小——在嵌入式、缓存紧张的场景仍有价值。
例 4:同一段逻辑的两种指令序列
求数组前 4 个元素之和不含第 5 个元素的
sum = a[0]+a[1]+a[2]+a[3](a的基址在$a0,sum存$v0)。分别写出 RISC 风格(MIPS32)与 CISC 风格的序列,比较指令条数与访存次数。
RISC 风格(MIPS32,Load-Store 结构)——已按本篇原则写成:定长 4 字节,只有 lw 访存:
asm
# RISC 风格: 4 条 lw + 4 条 add, 寻址方式只用"基址+偏移"一种
lw $t0, 0($a0) # 取 a[0]
lw $t1, 4($a0) # 取 a[1]
lw $t2, 8($a0) # 取 a[2]
lw $t3, 12($a0) # 取 a[3]
add $v0, $t0, $t1 # a[0]+a[1]
add $v0, $v0, $t2 # +a[2]
add $v0, $v0, $t3 # +a[3]CISC 风格(示意,仅表达设计思路;本站汇编一律以 MIPS32 为准,x86-64 只作对照附录):
asm
# CISC 风格示意: 内存可以直接参与算术运算, 并支持变址寻址
mov eax, [ebx] # 累加器 = a[0]
add eax, [ebx+4] # 直接加内存里的 a[1]
add eax, [ebx+8]
add eax, [ebx+12]对照统计:
| 指标 | RISC(MIPS32) | CISC(示意) |
|---|---|---|
| 指令条数 | 7 条 | 4 条 |
| 其中访存指令 | 4 条(全部是 lw) | 4 条(算术指令内嵌访存) |
| 指令字长 | 恒定 4 字节 → 总 28 字节 | 变长(示意,约 3~8 字节/条) |
| 目标码估算 | 28 字节(7 条 × 4 B) | 约 16 字节(4 条 × 4 B,示意) |
| 能否每条单周期 | 可以 | add eax,[ebx+4] 要"读内存+加+写回",往往多周期 |
结论:
- RISC 的多条简单指令换来"每条都能单周期、都能进流水线";代价是指令条数与目标码体积上升。
- CISC 的 4 条指令目标码更小,但其中 3 条包含访存,执行时间不齐,难以流水线。
- 两条路线的取舍点,浓缩在"指令条数 vs 单条执行时间"这一个乘积上。
例 5:Python——CPI 与执行时间的完整换算
def exec_time(n_instr, cpi, freq_hz):
"""返回 (周期数, 时间/秒)"""
cc = n_instr * cpi
return cc, cc / freq_hz
# ---- 例 1 / 例 2 ----
cfg = [('CISC', 100, 4.0, 2.0e9), ('RISC', 300, 1.2, 1.5e9)]
print('%-5s %6s %5s %8s %8s %10s' % ('机器', '条数', 'CPI', '周期数', '主频', '时间'))
res = {}
for name, n, cpi, f in cfg:
cc, t = exec_time(n, cpi, f)
res[name] = (cc, t)
print('%-5s %6d %5.1f %8.0f %6.1fGHz %8.1f ns' % (name, n, cpi, cc, f / 1e9, t * 1e9))
c, r = res['CISC'], res['RISC']
print('\n同主频(2GHz)下: CISC %d 周期 vs RISC %d 周期 -> RISC 快 %.2f 倍 (%.1f%%)'
% (c[0], r[0], c[0] / r[0], (c[0] / r[0] - 1) * 100))
print('计入主频后: CISC %.0f ns vs RISC %.0f ns -> CISC 快 %.2f 倍'
% (c[1] * 1e9, r[1] * 1e9, r[1] / c[1]))
# ---- 敏感度: RISC 主频要提到多少才追平 ----
f_needed = r[0] / c[1]
print('\nRISC 要追平 CISC 的 %.0f ns, 主频需达到 %.2f GHz (现 %.1f GHz, 需 +%.0f%%)'
% (c[1] * 1e9, f_needed / 1e9, cfg[1][3] / 1e9, (f_needed / cfg[1][3] - 1) * 100))
# ---- 例 3 的 80-20 累积分布(经验数值) ----
freq = [('前 1 条', 0.30), ('前 5 条', 0.70), ('前 10 条', 0.90), ('前 20 条', 0.96), ('全部', 1.00)]
print('\n动态指令频率累积(经验数值):')
for k, v in freq:
print(' %-8s 累计执行占比 %.0f%%' % (k, v * 100))
print(' 说明: 前 10 条占总执行量 90%, 后 10 条只贡献 6 个百分点 -> 边际收益骤减')
# ---- 例 4 的指令条数与体积 ----
risc_n, cisc_n, avg_b = 7, 4, 4
print('\n例 4: RISC %d 条 x %dB = %d B ; CISC %d 条 x %dB = %d B ; 体积比 %.2f'
% (risc_n, avg_b, risc_n * avg_b, cisc_n, avg_b, cisc_n * avg_b,
(risc_n * avg_b) / (cisc_n * avg_b)))
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出对照:CISC 400 周期 / 200 ns,RISC 360 周期 / 240 ns;同主频下 RISC 快 1.11 倍(11.1%),计入主频后 CISC 快 1.20 倍——与例 1、例 2 手算完全一致。敏感度一项说明 RISC 需要约 1.80 GHz 才能追平(
考点
考点
1. 必背结论
- RISC 五条原则:① 指令少且定长;② Load-Store(只有 load/store 访存);③ 寄存器多;④ 寻址方式少;⑤ 硬布线控制器(不用微程序)。
- CISC 代表:x86、VAX;RISC 代表:MIPS、ARM、RISC-V、PowerPC、SPARC。
- 定长的根本意义:取指边界确定 → 可细分流水线节拍。这是 RISC 与第 32 篇流水线的接口。
- 决定速度的是三项乘积:
。任何单项都不能单独判胜负(例 1 与例 2 正好是一对反例)。 - 现代 x86 = 外壳 CISC + 内核 RISC:复杂指令先译成微操作(uop)再进 RISC 式流水线。
2. 高频陷阱
- "RISC 指令少所以程序一定快"是错的。例 1 例 2 就是反证:同主频下 RISC 只赢 11%,主频一变就反超。答"性能"类问题必须把三项都代进去。
- "RISC 不用访存指令"是错的。准确说法是只有 Load/Store 访存,运算指令不能直接访问内存。这是"Load-Store 结构"四个字的确切含义。
- "RISC 指令少"要加"种类"二字。同一程序编译出的指令条数 RISC 反而更多(例 4:7 条 vs 4 条),少的只是指令种类的数量。这两个"条数"极易混淆,是命题人最喜欢的设陷点。
- "CISC 用微程序、RISC 用硬布线"是倾向,不是铁律。CISC 也可以用硬布线(现代 x86 大量用组合逻辑),RISC 也可以微程序化。答题时加"通常"二字更稳。
- "目标码更小"是 CISC 的优势不是缺点。别把 CISC 的优点漏掉。
- CISC 的 CPI 不是常数:不同指令差别巨大(
mov1 周期、div几十周期),所以"平均 CPI"必须由指令混合比例加权得到。
3. 解题模板("CISC/RISC 性能比较")
① 分别求两条路线的 指令条数 n 与 平均 CPI
② 周期数 CC = n x CPI <- 主频未给时到此为止
③ 若给了主频: T = CC / f
④ 比较 T (或 CC) 得出倍数
⑤ 若题目问"RISC 主频要多高才追平": f' = CC_RISC / T_CISC
⑥ 若题目问"平均 CPI": CPI = Σ(各类指令占比 × 该类 CPI)4. 与相邻章节的接口
- 第 20 篇:RISC 的"定长 + 寻址方式少"与 CISC 的"变长 + 寻址方式多"是同一组对立在指令格式上的体现。
- 第 22 篇:MIPS32 就是 RISC 的活标本——恒 4 字节、只有 R/I/J 三种格式、只有
lw/sw等访存指令。 - 第 31 篇:控制器一节会详讲"微程序 vs 硬布线",正是本章第五节那一条的展开。
- 第 32 篇:流水线是 RISC 真正的杀手锏——为什么 RISC 容易流水线,本篇"定长"那一段给出全部理由。
小结
- CISC 与 RISC 是设计哲学之争:CISC 用复杂指令换"程序条数少",RISC 用简单指令换"单条执行快"。
- RISC 五原则:定长指令、Load-Store、寄存器多、寻址方式少、硬布线。
- "定长"是 RISC 能流水线化的根:取指边界恒定,才能把流水线切成固定节拍。
- 性能永远是三项乘积
。例 1(同主频 RISC 赢 11%)与例 2(计入主频 CISC 赢 20%)是同一份程序的两个答案——只答单项的一律算错。 - 别把"指令种类少"说成"指令条数少":同一程序 RISC 编译出的指令条数更多。
- 今天的高性能 CPU 是融合体:前端吃 CISC 指令,后端按 RISC 微操作执行。
下一篇:指令编码实例:读懂一条机器码
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。