从单核性能瓶颈到多核、超线程——每一代技术都在解决前一代留下的问题,同时制造新的麻烦。这篇文章带你彻底搞懂。
CPU 设计的核心矛盾在于:程序的执行速度永远追不上人类的需要。每一代技术突破都在回答同一个问题——如何让 CPU 在单位时间做更多事?而每一次回答,又会引出新的问题。下面这张演进图勾勒出了整个技术树的脉络:
CPU 设计者的策略可以概括为三个阶段:
下面我们逐一拆解每个技术——它解决了什么、怎么解决、又带来了什么新坑。
早期 CPU 执行指令的方式是"取一条→译码→执行→写回→取下一条",这种串行方式导致 CPU 内大量硬件在执行某一步时处于闲置状态。就像工厂里的工人,一个人把所有工序都做完才能开始下一件——效率极低。
流水线的核心思想是拆分工序并重叠执行。将一条指令的执行分解为多个阶段(经典的五级流水:取指 → 译码 → 执行 → 访存 → 写回),每个阶段由独立的硬件负责。当第一条指令进入译码阶段时,第二条指令可以同时开始取指——就像工厂流水线一样并行作业。
即使有了流水线,每个时钟周期仍然只能发射一条指令。对于没有依赖关系的多条指令(比如同时计算 a+b 和 c+d),它们明明可以并行执行,却被"每周期一条"的发射宽度限制住了。
超标量架构让 CPU 在每个时钟周期可以同时发射多条指令到不同的执行单元。核心思想是硬件层面自动发现指令间的并行性——如果两条指令操作不同的寄存器、互不依赖,就可以同时发射到不同的功能单元(ALU、FPU、Load/Store 单元等)中执行。
| 架构 | 每周期发射宽度 | 执行单元配置 |
|---|---|---|
| Apple M1 (Firestorm) | 8 条 | 6 ALU + 2 分支 + 2 Load/Store + 2 FP/SIMD |
| Intel Golden Cove | 6 条 | 5 ALU + 2 分支 + 2 Load + 2 Store + 2 FP/SIMD |
| AMD Zen 4 | 6 条 | 4 ALU + 3 AGU + 2 分支 + 2 FP/SIMD |
| ARM Cortex-X3 | 6 条 | 4 ALU + 2 分支 + 2 Load/Store + 2 FP/SIMD |
💡 宽的发射宽度意味着硬件可以在指令窗口中找到更多可并行的指令,但这也大幅增加了前端的解码压力和后端的端口分配复杂度。
超标量发射解决了"可以发射多条",但没有解决"多条指令之间的等待"问题。在顺序执行中,如果第一条指令因为等内存数据而卡住(比如 cache miss),后面的所有指令——哪怕完全不依赖它——也必须跟着等。这就像银行只开一个窗口,排前面的顾客填表慢了,后面的人都得等着。
乱序执行的核心是"让就绪的指令先跑"。CPU 不再按照程序给出的顺序依次执行,而是维护一个"指令池"(重排序缓冲区 ROB),从中挑出操作数已经准备好的指令立即执行。执行结果暂存在 ROB 中,最后再按程序原始顺序依次写回寄存器——外部看来一切都有序,内部已经跑得天花乱坠。
乱序执行依赖三个关键结构:寄存器重命名(消除假的数据依赖)、重排序缓冲区 ROB(暂存乱序结果,保证顺序提交)、发射队列 / 调度器(从就绪指令中选择要发射的)。
程序中有大量的条件分支(if/else、循环、switch),平均每 5-7 条指令就有一条分支。在流水线中,分支指令要到执行阶段才能算出跳转目标,但流水线不能停——它必须提前决定下一条取哪里的指令。如果猜错,前面白取的指令全要丢弃,流水线越深代价越大。
分支预测器在取指阶段就猜测分支的方向和目标地址,让流水线沿着猜测的路径继续取指。现代分支预测器分为两部分:方向预测器(猜"跳不跳")和目标预测器(猜"跳到哪里")。常用技术包括:两位饱和计数器(2-bit saturating counter)、全局历史 + 模式匹配(GSHARE)、以及近年大热的感知器分支预测器(Perceptron)和TAGE 预测器,准确率可达 97%+。
CPU 的运算速度远快于内存访问速度——这就是著名的"存储墙"(Memory Wall)。一次 DDR5 内存访问大约需要 100ns(~200 个时钟周期),而期间 CPU 可以做几百次加法运算。如果每次访存都要等这么久,再快的超标量、再深的乱序执行也白搭。
缓存利用了局部性原理——程序倾向于重复访问最近用过的数据(时间局部性)和相邻的数据(空间局部性)。通过构建 L1 → L2 → L3 → 内存的分层结构,用少量高速 SRAM 缓存最常用的数据,用大量低速 DRAM 做后备,让大部分访存请求在纳秒级完成。
到 21 世纪初,单核性能提升遇到了三道不可逾越的墙——功耗墙(芯片散热已达到物理极限,Pentium 4 热密度接近核反应堆)、ILP 墙(指令级并行已被挖到极限,每增加 10% 性能需 100% 的复杂度)和存储墙(内存延迟与 CPU 速度的差距在拉大)。单纯提升主频和提高 IPC 的路线走到了尽头。
多核 CPU 的策略是"既然一个核心已经挖不出更多油水,那就多放几个核心"。在同一块芯片上复制多个完整的 CPU 核心,每个核心都有自己的 L1/L2 缓存,共享 L3 缓存和内存控制器。操作系统将不同进程/线程调度到不同核心上,实现真正的并行执行——不是模拟并行,不是指令级并行,而是物理上的、真正的多条指令流同时执行。
即使是最宽的超标量核心,在单线程执行时也经常出现功能单元闲置的情况——因为单个线程内的指令级并行性是有限的。比如 Intel 的核心有 6 发射宽度,但很多普通单线程代码的有效 IPC 只有 1.5 ~ 2.5,意味着 50%~75% 的执行单元在空转。多核 CPU 增加了核心数,但每个核心内部仍有大量浪费。
超线程(Intel 叫 Hyper-Threading,通用术语为 Simultaneous Multithreading / SMT)的做法很巧妙:让一个物理核心同时运行两个线程。操作系统看到的是两个"逻辑核心"(Logical Processor),但它们共用同一套执行单元、缓存和总线。当第一个线程因为缓存 miss 等原因停顿时,第二个线程的指令可以立即填入空闲的执行单元。相当于让两个线程轮流用同一个核心的闲置资源。
SMT 要做的硬件改动其实不大——主要是复制一份架构状态(PC、寄存器堆),让取指阶段从两个线程交替取指。执行单元、ROB、缓存都是共享的。Intel 实现了 2 路 SMT,IBM POWER 做到了 4 路甚至 8 路。
点击下面的按钮,在浏览器中运行一个简单的多线程计算来感受 SMT 的效果。
这个实验用多个 Worker 线程计算素数,模拟 CPU 密集型任务在 SMT 环境下的行为。
| 技术 | 解决什么问题 | 核心原理 | 带来的新问题 |
|---|---|---|---|
| 流水线 | 串行执行浪费硬件资源 | 拆分工序,多指令重叠执行 | 数据/控制/结构冒险;流水线冲洗代价 |
| 超标量 | 每周期一条指令的发射带宽瓶颈 | 多执行单元 + 宽发射,发掘 ILP | 调度复杂度 NP-hard;功耗平方级增长 |
| 乱序执行 | 前一条指令卡住,后面的全等 | 寄存器重命名 + ROB,就绪指令先跑 | 功耗暴涨;设计验证困难;Spectre 漏洞 |
| 分支预测 | 流水线遇到分支不知道该取什么 | 基于历史/模式提前猜测分支走向 | 误预测刷新代价;推测执行攻击载体 |
| 缓存层次 | 存储墙——内存比 CPU 慢百倍 | 利用局部性原理分层缓存数据 | 一致性协议开销;旁路攻击;NUMA 效应 |
| 多核 CPU | 功耗墙 + ILP 墙,单核性能挖尽 | 复制多个完整物理核心,真正并行 | 阿姆达尔定律;缓存一致性;编程复杂度 |
| 超线程 / SMT | 单线程难以填满宽超标量核心 | 一个物理核同时运行两个线程 | 安全漏洞;资源争抢;调度器不感知物理拓扑 |
如果你还想深入,以下是值得探索的方向: