复杂指令系统计算机(CISC)

CISC 不是”设计得不好的 ISA”,它是在特定资源约束下的合理解。要看懂 4.4,得先看懂它当年在优化什么——因为 RISC 之所以能赢,正是那三条约束全部消失了。

机制

三条历史约束

约束(1970 年代)导致的设计选择
主存极贵、极小(以 KB 计)代码密度压倒一切——能少一个字节是一个字节
编译器不成熟希望硬件提供接近高级语言语义的指令,缩小”语义鸿沟”(如一条指令完成数组访问、循环、串比较)
微程序控制器普及加一条复杂指令的边际成本极低——只要写一段微程序,不必改硬件

第三条是关键的加速器。 在微程序控制器(5.4 控制器(本地资料))下,指令集扩充变成了”往控制存储器里多烧一段微码”,几乎免费。于是指令集只增不减,几百条是常态。

典型特征

维度CISC 的做法
指令数量多,数百条
指令长度可变(x86:1~15 字节)
指令格式多种,字段位置不固定
寻址方式多,十几种
访存几乎任何指令都能直接访存
指令周期不等长,多数指令需多个时钟周期
通用寄存器少(IA-32 只有 8 个)
控制器典型搭配微程序控制
优化重心硬件(复杂指令由硬件/微码实现)

“几乎任何指令都能访存”这一条最要紧,它是 CISC 与 RISC 最实质的分界,后果会在 4.4.2 展开。

复杂指令值不值:本章已经给过一正一反两个例证

关联对照: lea 和 loop,同样是复杂指令,一个赢一个输

lea(4.3.1)loop(4.3.3)
一条顶几条顶 4 条顶 2 条
硬件是否优待它✅ 有专门的 AGU 通路,不占 ALU 端口❌ 微码实现,且隐式改 ECX 妨碍寄存器分配
实测与 add 同延迟或略长,但吞吐更好比 dec+jnz 更慢
编译器大量生成几乎从不生成

判据是一句话:一条复杂指令的价值,取决于硬件有没有真的为它建一条专用通路。 只是”把几条指令打包成一条微程序”的复杂指令,省的是取指带宽,赔的是执行灵活性,多数时候不划算。

这正是 RISC 论证的核心:既然大多数复杂指令并没有被硬件优待,不如干脆不要它们,把省下的硬件预算花在流水线和寄存器上。

二八定律:RISC 论证的实测依据

1970 年代末的统计发现:

程序 80% 的运行时间,只用到了大约 20% 的指令。

换句话说,指令集里那 80% 的复杂指令,只贡献了 20% 的执行时间,却让译码器、控制器、流水线全部为它们付出复杂度代价。

这条统计结论是 RISC 的直接触发点——它把”复杂指令好不好”从哲学争论变成了一个可测量的工程问题。

变长指令的译码代价

4.1.4 立下的主线在这里结算:变长指令省存储空间,代价是取指与译码的串行依赖。

x86 把这个代价推到了极致——一条指令由若干前缀字节 + 操作码 + ModRM 字节 + SIB 字节 + 位移 + 立即数拼成,每一段的存在与否都由前一段决定:

graph LR
    A["取第 1 字节"] --> B{"是前缀吗?"}
    B -->|是| A
    B -->|否| C["这是操作码"]
    C --> D{"需要 ModRM 吗?"}
    D -->|是| E["取 ModRM"] --> F{"需要 SIB 吗?"}
    D -->|否| G
    F -->|是| H["取 SIB"] --> G
    F -->|否| G
    G["取位移/立即数"] --> I["★ 到此才知道指令有多长"]
    classDef hot fill:#f8cecc,stroke:#b85450,stroke-width:2px
    class I hot

要知道第 2 条指令从哪开始,必须先把第 1 条译到最后。 这在单发射机器上只是慢一点,在超标量机器上是致命的——一拍要发射 4 条指令,就得先切分 4 条指令的边界。现代 x86 为此专门加了长度预译码级和指令边界标记缓存,这些电路 RISC 完全不需要。

现代 x86 的内部 RISC 化

x86 至今仍是 CISC ISA,但它的微体系结构早已是 RISC 式的:

指令译码器微操作定长、类乱序引擎执行

这是 4.1.1 那张三层图最有力的一个例证:ISA 层必须保持 CISC(否则几十年的软件全部作废),微体系结构层可以完全换成 RISC 风格。“契约不变,实现随便换”——ISA 存在的意义在这里体现得最彻底。

代价也很清楚:那个译码器就是 x86 为向后兼容付出的常驻硬件成本,它一直在那儿耗电、占面积,无论跑什么程序。

边界

“CISC 用微程序控制器”是典型搭配,不是定义。 CISC 的定义在指令集的复杂度,不在控制器的实现方式。现代 x86 处理器对简单指令用硬布线快速译码、只对复杂指令走微码——两种控制方式在同一颗芯片里共存。判断题若说”CISC 必须采用微程序控制器”,错。

指令条数多不是 CISC 的本质,“单条指令功能复杂 + 任意指令可访存”才是。 现代 RISC-V 加上各种扩展后指令条数并不少,但它仍是 RISC,因为每条指令依然简单、依然只有 load/store 访存。

代码密度高是 CISC 至今没有被淘汰的一个真实理由。 指令占的空间小 → 同样大小的指令 Cache 能装下更多指令 → 指令 Cache 命中率更高(3.5.2)。在存储器已经不贵的今天,这条理由从”省内存”变成了”省 Cache”,但它依然成立。

CISC 的复杂指令并非都没用。 串操作指令(REP MOVSB)在现代 x86 上被专门优化过,拷贝大块内存时比手写循环还快——又是”硬件有没有优待它”这条判据。

“语义鸿沟”这个动机今天基本失效了。 编译器的优化能力早已超过手写汇编,用多条简单指令合成的代码往往比一条复杂指令更快(因为调度空间更大)。这是三条历史约束里消失得最彻底的一条。

对照速查

维度CISC
指令数量多(数百条)
指令长度可变
寻址方式多(十几种)
访存任意指令均可
指令周期不等长、多周期
通用寄存器少
控制器典型微程序(非定义)
优化重心硬件
代码密度高
判断对错
CISC 必须用微程序控制器❌(典型搭配,非定义)
CISC 的本质是指令条数多❌(是单条功能复杂 + 任意指令可访存)
CISC 代码密度高✅
代码密度高对 Cache 有利✅
二八定律说 80% 的指令占 20% 的时间⚠️ 反着说也对:20% 的指令占 80% 的时间
现代 x86 已经不是 CISC❌(ISA 仍是 CISC,微架构是 RISC 式)
复杂指令一定比等价的简单指令序列快❌(loop 就是反例)

考点

  • CISC 的三条历史动因:主存贵(代码密度)、编译器弱(语义鸿沟)、微程序使扩充廉价
  • 特征:指令多、变长、寻址方式多、任意指令可访存、多周期、寄存器少、典型配微程序
  • “任意指令可访存”是与 RISC 最实质的分界
  • 二八定律:20% 的指令占 80% 的运行时间 → RISC 的实测依据
  • 变长指令的代价是译码串行,超标量下必须加长度预译码
  • 现代 x86:ISA 是 CISC,微体系结构是 RISC 式(译码成 μops)——ISA 三层的最佳例证
  • 判据:复杂指令值不值,看硬件有没有为它建专用通路(lea 赢、loop 输)
  • 微程序控制是典型搭配不是定义

链接