Skip to content

超标量处理器概览

2004年,Intel取消了代号为Tejas的下一代Pentium 4处理器。这颗芯片的设计目标是7 GHz,但散热问题使之永远停留在图纸上。那一天,处理器设计的"频率竞赛"宣告终结。此后的二十年,处理器性能的提升不再来自更高的时钟频率,而是来自更聪明的微架构——用更宽的发射、更深的投机和更精确的预测来榨取每一个时钟周期的价值。这就是超标量处理器的故事。

设计提示

本书的统一视角。处理器设计的本质是在有限的晶体管预算和功耗约束下,通过投机(speculation)和并行(parallelism)的层层叠加来逼近指令吞吐率的理论上限。本章将引入性能公式T=N×CPI×TcycleT = N \times \mathrm{CPI}\times T_{\text{cycle}},其三个因子分别对应:

  • 指令级并行IPC=1/CPI\mathrm{IPC}= 1/\mathrm{CPI})——通过更宽的发射和乱序执行挖掘并行性;

  • 微架构投机——通过分支预测、数据预取等投机机制降低CPI\mathrm{CPI}中的分支惩罚和缓存缺失分量;

  • 工艺约束TcycleT_{\text{cycle}})——半导体工艺决定了时钟周期的物理下限。

这三个维度将贯穿全书的每一个设计决策。

为什么需要超标量

如何才能让一个程序执行得更快?在计算机领域有这样一个经典的公式来计算一个程序的执行时间:

执行时间=指令数×CPI×时钟周期 \text{执行时间} = \text{指令数} \times \mathrm{CPI}\times \text{时钟周期}

其中,指令数(Total Instructions)表示程序总共需要执行的指令个数,CPI\mathrm{CPI}(Cycles Per Instruction)表示执行每条指令所需要的平均周期数,时钟周期表示每个周期所需要的时间。从上面的公式可以看出,有三个因素影响了程序执行时间,如图图 1.1所示。

衡量一个程序执行时间的三个因素
衡量一个程序执行时间的三个因素

(1)减少程序中指令的数量。这取决于程序本身所要完成的工作量、实现某个功能所选择的算法、编译器强大与否,甚至是指令集是否对某些特殊的功能有扩展。例如使用AVX-512向量指令可以将一个循环中需要数十条标量指令完成的工作压缩到几条向量指令中;RISC-V的V扩展和ARM的SVE扩展也具有类似的能力。但是对于那些已经编写好的固定程序,这个变量就变成定值了。

(2)减少每条指令在处理器中执行所需要的周期数,也就是减少CPI,这也意味着增加IPC(Instructions Per Cycle)。CPI和IPC互为倒数:CPI表示处理器执行一条指令所需要的周期数,IPC则表示处理器在一个周期内可以执行的指令个数。对于非流水线的处理器,需要多个周期才能执行一条指令;对于简单的标量流水线处理器,每周期最多执行一条指令,因此IPC最大也就是1。要想每周期执行多于一条的指令,就需要使用超标量(Superscalar)处理器。超标量处理器每周期可以从I-Cache中取出多于一条的指令送到流水线中执行,从而大大提高IPC的值。处理器采用何种架构来实现也称为微架构(Microarchitecture),它直接影响了处理器的性能,本书重点关注的内容是超标量处理器的微架构设计。

(3)减少处理器的周期时间(cycle time)。在进行处理器的微架构设计时,可以通过精巧的电路设计、更深的流水线来减少周期时间。在3 nm工艺下,高性能处理器的时钟频率已经可以超过5 GHz,而在2 nm工艺下有望进一步提高。但是频率的提升受限于功耗和散热:处理器的动态功耗与频率和电压的平方成正比(P=αCV2fP = \alpha C V^2 f),单纯提频带来的功耗增长是超线性的。Dennard缩放定律的终结使得这个问题更加严峻——在先进工艺下,晶体管的漏电功耗占比越来越大,提高频率的收益不断递减。

现代高性能处理器的发展路径已经从"追求更高频率"转向了"追求更高IPC"。在处理器的发展史中,超标量处理器的发射宽度经历了从2-way(如1993年的Intel Pentium)到4-way(如2006年的Intel Core 2)、6-way(如2020年的AMD Zen 3、2021年的Intel Golden Cove)再到8-way(如2020年的Apple Firestorm)的演进过程。与此同时,流水线深度也从早期的5\sim8级发展到现代的15\sim25级。在这样的宽度和深度下,处理器内部同时存在上百条正在执行的指令,如何高效地管理这些指令的执行、解决它们之间的各种相关性、并在预测失败时快速恢复状态,正是超标量处理器设计的核心挑战。

正是由于超标量处理器天生的复杂性,并没有一套放之四海而皆准的设计原则,很多时候需要处理器设计师根据实际的需求做出各种折中(tradeoff)。例如要实现一个精确的TAGE分支预测器需要大量的存储资源和复杂的匹配逻辑;更宽的发射队列需要更多端口的CAM结构,而CAM的面积和功耗随端口数呈超线性增长;更大的ROB可以容纳更多的飞行中指令(in-flight instructions),但也带来更大的面积和更长的恢复延迟。要想获得更好的性能,就需要更复杂的设计和更多的硬件资源,由此带来更高的成本和功耗。在现实世界的处理器中,需要根据目标市场——服务器、桌面、移动还是嵌入式——做出相应的折中方案。

影响处理器性能的因素

程序执行时间的公式

由公式式 (1.1)可以进一步展开:

执行时间=指令数×CPIf \text{执行时间} = \frac{\text{指令数} \times \mathrm{CPI}}{f}

其中ff为处理器的时钟频率。对于一个给定的程序,指令数取决于ISA和编译器,而CPI和频率则取决于微架构设计。一个优秀的处理器设计应当追求尽可能低的CPI/f\mathrm{CPI}/ f的比值,也就是尽量同时提高IPC和频率。

在实际的处理器设计中,IPC并非一个固定的值,它取决于程序的特征。例如一个整数运算密集的程序和一个浮点运算密集的程序,在同一个处理器上的IPC可能相差很大。前者的指令之间通常有较多的数据依赖,限制了并行度;后者如果能充分利用向量单元,则可能获得更高的IPC。因此在评估处理器性能时,通常使用一组标准的基准测试程序来综合评估,例如SPEC CPU 2017(涵盖整数和浮点)、Geekbench(面向移动和桌面)、MLPerf(面向AI工作负载)等。

CPI与IPC的含义

CPI和IPC是描述处理器效率的核心指标。对于一个nn-way的超标量处理器,其理想的IPC=n\mathrm{IPC}= n,即每个周期完成nn条指令的提交。但是由于指令之间的数据相关性、Cache缺失、分支预测失败、功能单元冲突等原因,实际的IPC总是远小于nn的。

表 1.1列出了截至2025年主要高性能处理器的微架构参数和典型IPC。

处理器工艺解码宽度流水线级数ROB容量典型IPC频率
Intel Golden CoveIntel 76-wide\sim205123.5–4.55.2 GHz
Intel Lion CoveIntel 48-wide\sim225764.0–5.05.5 GHz
AMD Zen 4TSMC 5nm6-wide\sim193203.5–4.55.7 GHz
AMD Zen 5TSMC 4nm8-wide\sim204484.0–5.05.8 GHz
Apple Firestorm (M1)TSMC 5nm8-wide\sim166304.5–5.53.2 GHz
Apple Everest (M4)TSMC 3nm10-wide\sim17700+5.0–6.04.4 GHz
ARM Cortex-X4TSMC 4nm6-wide\sim173203.0–4.03.4 GHz
香山昆明湖TSMC 14nm6-wide\sim182562.5–3.52.5 GHz

现代高性能处理器的微架构参数与典型IPC(截至2025年)

从表表 1.1中可以看出几个重要趋势:

第一,现代高性能处理器的解码宽度已经从6-wide演进到8-wide甚至10-wide。Apple的处理器一直采用最激进的宽度设计,Apple Everest核心达到了10-wide解码。Intel和AMD也在最新的设计中将宽度提升到了8-wide(Intel Lion Cove和AMD Zen 5)。更宽的解码意味着每周期可以向后端供给更多的指令,但也带来了解码逻辑、重命名映射表、发射队列等部件的复杂度急剧上升。

第二,ROB的容量已经从早期的几十项增长到了500\sim700项。更大的ROB可以容纳更多的飞行中指令,使处理器能够"看到"更远的指令窗口,从而找到更多可以并行执行的指令。但是更大的ROB也意味着分支预测失败或异常发生时,需要清除更多的指令,恢复的代价更大。

第三,Apple的处理器以较低的频率获得了最高的IPC。这体现了一种设计哲学上的差异:Apple更偏向于通过增加微架构宽度和深度来提高IPC,而将频率控制在较低水平以降低功耗(PV2fP \propto V^2 f);而Intel和AMD则在IPC和频率之间取得更均衡的折中,频率可以超过5.5 GHz。

三个因素之间的制约关系

影响处理器性能的三个因素——指令数、CPI和时钟频率——并不是相互独立的,它们之间存在着复杂的制约关系:

(1)指令数与CPI的关系。更强大的指令集可以用更少的指令完成同样的任务,但每条指令通常更复杂,需要分解为更多的微操作(micro-op)。例如x86的REP MOVSB指令可以实现一个完整的内存拷贝循环,只需要一条指令,但在处理器内部需要展开为数十甚至上百条微操作来执行。而RISC-V的相同操作需要显式地写一个循环,指令数更多,但每条指令都简单,通常只产生一条微操作。现代x86处理器内部将CISC指令翻译为类RISC的微操作来执行,本质上是一个RISC核心包裹在CISC外壳之中,因此指令数和CPI的关系需要从微操作的层面来理解。

(2)IPC与时钟频率的关系。更高的IPC通常需要更复杂的微架构:更宽的发射宽度需要更多端口的CAM/SRAM结构,更大的ROB需要更多的比较逻辑,更精确的分支预测器需要更大的存储表。这些复杂结构会增加关键路径的延迟,导致时钟频率难以提高。Intel的Pentium 4处理器(NetBurst微架构,2000年)是这方面的经典反面教材:通过31级的超深流水线获得了当时最高的频率(高达3.8 GHz),但IPC极低,分支预测失败的惩罚高达20\sim30个周期,最终每瓦性能远不如流水线较浅的竞争对手。这个教训深刻地影响了后续所有处理器的设计:现代高性能处理器的流水线深度普遍控制在15\sim22级之间,在IPC和频率之间取得平衡。

(3)频率与功耗的关系。处理器的动态功耗公式为:

Pdynamic=αCV2f P_{\text{dynamic}} = \alpha C V^2 f

其中α\alpha为活动因子,CC为负载电容,VV为供电电压,ff为时钟频率。在先进工艺节点下(5 nm以下),漏电功耗也变得不可忽视:

Ptotal=Pdynamic+Pleakage=αCV2f+VIleakP_{\text{total}} = P_{\text{dynamic}} + P_{\text{leakage}} = \alpha C V^2 f + V \cdot I_{\text{leak}}

要提高频率通常需要提高电压,这使得总功耗以超线性的速度增长。在3 nm工艺下,一个8-wide的超标量核心在5 GHz下的功耗可以达到30 W以上,这对散热设计提出了严峻的挑战。因此现代处理器普遍采用了DVFS(Dynamic Voltage and Frequency Scaling)技术,根据工作负载动态调节电压和频率,在高负载时提升频率以获得性能,在低负载时降低频率以节省功耗。

性能分析 1 — IPC提升 vs. 频率提升的性能权衡

从性能公式PerformanceIPC×f\text{Performance} \propto \mathrm{IPC}\times f出发,我们可以量化IPC和频率两条路径各自的收益。下表以IPC=1.0\mathrm{IPC}=1.0f=3GHzf=\SI{3}{GHz}作为基准(归一化性能=1.0=1.0),列出不同IPC和频率组合的相对性能。

1.02.03.04.0
3 GHz1.002.003.004.00
4 GHz1.332.674.005.33
5 GHz1.673.335.006.67

关键观察:将IPC从1.0提升到4.0带来4×4\times性能增益;将频率从3 GHz提升到5 GHz仅带来1.67×1.67\times增益。但功耗代价截然不同:IPC提升主要消耗面积(更宽的发射、更大的ROB),功耗增长约为n1.5n^{1.5};频率提升则需要同时抬高电压,功耗增长为V2ff3V^2 f \approx f^3(在电压与频率近似线性的区间内)。这就是为什么2004年后的处理器工业从"频率竞赛"转向了"IPC竞赛"——在相同的功耗预算下,提升IPC的性价比远高于提升频率。

现代超标量处理器的流水线

从经典五级流水线到现代深流水线

经典的RISC处理器采用五级流水线(取指-解码-执行-访存-写回),每周期启动一条新指令的执行。这种设计在1980\sim1990年代的处理器中被广泛使用,如MIPS R3000、SPARC等。关于五级流水线的基本概念,可以参阅教科书[patterson2020],此处不再赘述。

现代的超标量处理器已经远远超越了五级流水线的范畴。以一个典型的6-way乱序超标量处理器为例,其流水线通常包含15\sim22个阶段,如图图 1.2所示。

一个典型的现代超标量处理器流水线(简化为14级)
一个典型的现代超标量处理器流水线(简化为14级)

需要强调的是,图图 1.2是一个高度简化的示意图。实际的处理器流水线远比图中所示的更加复杂:取指阶段可能占3\sim4级(分支预测本身就需要多个周期),解码阶段可能占2\sim3级(x86处理器还需要额外的预解码阶段来处理变长指令),执行阶段的级数取决于功能单元的类型(整数加法通常为1个周期,整数乘法为3\sim4个周期,浮点运算为4\sim6个周期),加载指令还需要额外的Cache访问周期。因此实际的流水线深度(从分支预测到执行结果产生)通常在15\sim22级之间。

表 1.2给出了几款现代处理器的流水线阶段划分的近似值。

分支预测取指解码重命名/分发发射/执行写回合计
Intel Golden Cove2–333–421+2–3\sim20
AMD Zen 4232–421+2–3\sim19
Apple Firestorm222–321+2\sim16
ARM Cortex-X422–3221+2\sim17

现代处理器的流水线阶段划分(近似值)

从表表 1.2可以看出,Apple Firestorm的流水线级数(\sim16级)明显少于Intel Golden Cove的(\sim20级),这也解释了为什么Apple的处理器时钟频率较低——较浅的流水线意味着每一级的延迟预算更大,不需要太高的频率就能完成每级的逻辑运算。而Intel选择了更深的流水线来换取更高的频率。这两种设计策略各有优劣:更浅的流水线在分支预测失败时惩罚较小(因为需要清除的流水段较少),但频率受限;更深的流水线可以获得更高频率,但分支预测失败的代价更大。

流水线深度的权衡

流水线深度的选择是处理器设计中最重要的早期决策之一。一个直觉性的问题是:为什么不能无限地增加流水线深度来提高频率?答案涉及到Cost/Performance的数学分析。

当处理器没有使用流水线时,需要消耗的硬件面积是GG,电路延迟为DD。当使用了nn级流水线之后,总硬件面积变为G+n×LG + n \times LLL为每级流水线寄存器的面积开销),频率变为1/(D/n+S)1/(D/n + S)SS为流水线寄存器的延迟)。Cost/Performance的表达式为:

CostPerformance=(G+nL)×(Dn+S)=GDn+SLn+LD+GS \frac{\text{Cost}}{\text{Performance}} = (G + nL) \times \left(\frac{D}{n} + S\right) = \frac{GD}{n} + SLn + LD + GS

对其求导并令其为零,可以得到最优流水线级数:

nopt=GDLSn_{\text{opt}} = \sqrt{\frac{GD}{LS}}

但上述分析忽略了一个关键因素:分支预测失败的惩罚。在现代超标量处理器中,分支预测失败时需要清除从预测点到执行点之间所有流水段中的指令。如果流水线有nn级,前端到分支解析点之间有kk级,则每次预测失败的惩罚约为kk个周期。假设分支指令的比例为pbp_b,预测失败率为mm,则由于分支预测失败导致的额外CPI为:

CPIbranch=pb×m×k \mathrm{CPI}_{\text{branch}} = p_b \times m \times k

对于现代的TAGE分支预测器,mm通常在2%\sim5%之间(取决于工作负载)。假设pb=0.15p_b = 0.15(每6\sim7条指令有一条分支),m=0.03m = 0.03k=15k = 15级,则:

CPIbranch=0.15×0.03×15=0.0675\mathrm{CPI}_{\text{branch}} = 0.15 \times 0.03 \times 15 = 0.0675

这意味着仅分支预测失败一项就会使IPC下降约6.75%。如果流水线更深(k=25k = 25),则CPIbranch=0.1125\mathrm{CPI}_{\text{branch}} = 0.1125,性能损失增加到11.25%。这正是为什么Intel在Pentium 4之后放弃了超深流水线设计,转而回到了15\sim22级的范围。

现代处理器流水线的各阶段

一个典型的现代乱序超标量处理器的流水线可以划分为以下几个大的阶段。每个阶段将在后续章节中详细讨论,此处仅作概述。

(1)分支预测与取指令(Branch Prediction & Fetch)

这是流水线的最前端。分支预测器(Branch Predictor)在每个周期预测下一组要取的指令的地址,包括分支的方向(跳转还是不跳转)和目标地址。预测的结果被送到I-Cache,从中取出一个取指块(fetch block),通常为32字节或64字节宽。在现代处理器中,分支预测本身就需要2\sim3个流水段:第一级进行BTB(Branch Target Buffer)查询获取粗略的预测结果,后续级使用更精确但更慢的TAGE或感知机预测器进行细化。I-Cache的访问也需要2\sim3个周期。

Intel和AMD的x86处理器还配备了微操作缓存(micro-op cache 或 decoded stream buffer),如果所需指令已经在微操作缓存中命中,可以绕过解码阶段直接供给后端,这对于热点代码可以显著减少前端延迟。

(2)解码(Decode)

取出的指令需要进行解码,识别出指令的类型、源寄存器、目的寄存器和立即数等信息。对于RISC指令集(如RISC-V和AArch64),由于指令编码固定为32位(RISC-V的C扩展为16位),解码逻辑相对简单,通常只需1\sim2个流水段。而对于x86指令集,由于指令长度不定(1\sim15字节),首先需要一个预解码阶段来确定指令的边界,然后才能进行真正的解码。x86的解码器通常由多个简单解码器和一个复杂解码器组成(例如Intel的4-1-1-1结构),复杂指令则需要查询微码ROM(Microcode ROM)来展开为多条微操作。

在这个阶段,还会进行指令融合(instruction fusion)优化:例如将一条比较指令和紧随其后的条件分支指令合并为一条融合微操作(macro-fusion),减少后端需要处理的微操作数量。

(3)寄存器重命名与分发(Register Renaming & Dispatch)

解码后的指令(或微操作)进入寄存器重命名阶段。此阶段将指令中使用的架构寄存器(如RISC-V的x0\simx31、x86的RAX等)映射到处理器内部的物理寄存器。物理寄存器的数量远多于架构寄存器(例如Intel Golden Cove拥有280个整数物理寄存器),通过这种映射可以消除指令之间的WAR和WAW假相关性,使更多指令可以并行执行。

重命名完成后,指令被分发(dispatch)到发射队列(Issue Queue)和重排序缓冲区(ROB)中。分发阶段会检查各种资源是否可用:ROB是否有空闲表项、发射队列是否有空间、Store Buffer是否已满等。如果任何资源不可用,流水线前端会暂停(stall),直到资源释放。

(4)发射与执行(Issue & Execute)

发射队列是顺序执行和乱序执行的分界线。指令进入发射队列后,不再受程序顺序的约束——只要操作数准备好了,就可以被发射到功能单元(FU)中执行。发射队列中的唤醒(wakeup)逻辑负责监测每条指令的源操作数是否已经就绪,选择(select)逻辑负责从就绪的指令中选出要发射的指令。

现代处理器通常有10\sim14个执行端口,连接到不同类型的功能单元:整数ALU(通常4\sim6个)、整数乘法器、浮点/SIMD单元(通常2\sim4个)、分支单元、加载单元(通常2\sim3个)、存储单元(通常1\sim2个)等。不同类型的指令在FU中的执行延迟不同:整数加法为1个周期、整数乘法为3\sim4个周期、浮点乘加(FMA)为4\sim5个周期、L1D Cache加载为4\sim5个周期。

(5)写回与提交(Write Back & Commit)

指令在FU中执行完毕后,结果写入物理寄存器文件,同时通过旁路网络(bypass network)将结果前递到正在等待该结果的其他指令。指令的完成状态也会被标记到ROB中。

提交(commit)阶段是乱序执行恢复到顺序的关键。ROB按照程序的原始顺序检查队头的指令是否已经完成执行:如果完成且没有异常,则该指令退休(retire),其结果成为架构层面可见的状态;如果遇到异常或分支预测失败,则从该点开始清除所有后续指令,将处理器状态恢复到该指令之前的正确状态。

指令间的相关性

在讨论超标量处理器如何并行执行指令之前,首先需要理解指令之间存在的各种相关性,它们是限制处理器并行度的根本因素。

先写后读(RAW)

先写后读(Read After Write,RAW),也称为真相关(true dependence)。一条指令的源操作数来自于前面某条指令的结果,必须等到该结果计算完毕后才能执行:

asm
mul  x1, x2, x3    # 指令A: x1 = x2 * x3  (3周期延迟)
    add  x4, x1, x5    # 指令B: x4 = x1 + x5  (依赖A的结果)
    srl  x6, x1, x7    # 指令C: x6 = x1 >> x7 (也依赖A的结果)

在上面的例子中,指令B和C都依赖于指令A的结果x1。由于MUL指令的延迟为3个周期,指令B和C都必须等待3个周期后才能获得x1的值。但指令B和C之间没有相关性,它们可以在同一个周期并行执行。在超标量处理器中,一旦指令A的结果在FU中计算完毕,旁路网络会立即将结果前递给指令B和C,而不需要等到A的结果写入寄存器文件。

RAW相关性形成了程序中的关键路径(critical path),也称为依赖链(dependency chain)。依赖链的长度是限制IPC的最根本因素之一——即使处理器有无限宽的发射宽度和无限大的指令窗口,IPC也无法超过依赖链所允许的并行度上限。

先读后写(WAR)

先读后写(Write After Read,WAR),也称为反相关(anti-dependence):

asm
add  x1, x2, x3    # 指令A: 读取x2
    mul  x2, x5, x4    # 指令B: 写入x2

在程序顺序中,指令A必须先读取x2的旧值,然后指令B才能将新值写入x2。但这种相关性是"假"的——它仅仅因为两条指令碰巧使用了同一个寄存器名字x2。如果将指令B的目的寄存器改为另一个名字(例如x20),则这两条指令之间完全没有相关性,可以并行执行。这正是寄存器重命名所做的工作。

先写后写(WAW)

先写后写(Write After Write,WAW),也称为输出相关(output dependence):

asm
add  x1, x2, x3    # 指令A: 写入x1
    mul  x1, x5, x4    # 指令B: 也写入x1

在程序顺序中,指令B的写入必须在指令A之后发生,以保证x1的最终值是指令B的结果。但如果指令A和B被重命名为写入不同的物理寄存器(例如A写入P37,B写入P42),则它们可以并行执行甚至乱序完成,只要保证在提交时正确更新映射关系即可。

WAR和WAW合称为"假相关"或"命名相关"(name dependence),通过寄存器重命名可以完全消除。在现代超标量处理器中,物理寄存器的数量通常是架构寄存器的5\sim10倍(例如RISC-V有32个整数架构寄存器,而一个高性能RISC-V核心可能有200个以上的整数物理寄存器),这为消除命名相关提供了充足的空间。关于寄存器重命名的详细设计将在第第 24.0 章\sim第 26.0 章章中讨论。

控制相关性

控制相关性(control dependence)由分支指令引起。在超标量处理器的深流水线中,分支指令从取指到解析结果(在执行阶段完成条件判断)可能跨越15个以上的流水段。在此期间,处理器已经按照预测的方向取了数十条指令。如果预测错误,所有这些指令都必须被丢弃,流水线被清空后从正确的地址重新开始取指。

以一个20级流水线的处理器为例,分支预测失败的惩罚约为15\sim20个周期。假设程序中每6条指令有一条分支,预测准确率为97%,则平均每200条指令发生一次预测失败,损失约15\sim20个周期的工作。这意味着仅分支预测失败一项就可以使IPC下降约8%\sim10%。如果预测准确率下降到95%,损失将翻倍。因此现代处理器在分支预测器上投入了大量的硬件资源,使用TAGE、感知机等复杂的预测算法来达到97%以上的准确率。关于分支预测的详细设计将在第第 13.0 章\sim第 17.0 章章中讨论。

存储器地址的相关性

除了寄存器之间的相关性外,load/store指令之间通过存储器地址也存在相关性:

asm
sd   x1, 0(x5)     # 指令A: MEM[x5] = x1
    ld   x2, 0(x6)     # 指令B: x2 = MEM[x6]

通过直接观察指令编码,很难判断这两条指令是否存在相关性——只有当x5x6的值相等时,指令B才依赖于指令A(RAW through memory)。这种相关性与寄存器相关性的本质区别在于:寄存器的名字在解码阶段就已知,而存储器的地址必须等到执行阶段才能计算出来。

在现代超标量处理器中,Memory Disambiguation机制用来处理这种相关性。保守的方法是让所有load指令等待之前所有store指令的地址计算完毕后再执行,但这会严重限制IPC。激进的方法是推测性地假设load和之前的store不冲突,让load提前执行;如果后来发现确实存在冲突,则需要回滚和重新执行。Store Set预测器等技术被用来精确地预测哪些load-store对可能冲突,从而在精度和性能之间取得平衡。这些内容将在第第 36.0 章章中详细讨论。

超标量处理器的分类

顺序执行的超标量处理器

在顺序执行(in-order)的超标量处理器中,指令的发射和执行严格遵循程序顺序。这种设计的优点是硬件简单、功耗低、面积小,适合移动和嵌入式场景。其典型代表包括ARM Cortex-A55/A510(效率核)、Intel Gracemont(E-core)等。

顺序超标量处理器的主要瓶颈在于:当一条长延迟指令(如L1D Cache缺失的load,延迟可达数十周期)阻塞流水线时,后续所有指令——即使与该load完全无关——都无法继续执行。这导致顺序处理器的IPC通常只有乱序处理器的40%\sim60%。

乱序执行的超标量处理器

乱序执行(out-of-order,OoO)是现代高性能处理器的标配。其核心思想是:只要操作数准备好了,指令就可以被发射执行,不必等待程序顺序中排在前面的指令完成。如表表 1.3所示,乱序处理器的流水线可以分为三个域。

前端发射/执行写回提交
顺序超标量顺序顺序顺序顺序
乱序超标量顺序乱序乱序顺序

顺序执行和乱序执行超标量处理器的比较

乱序处理器需要额外的硬件来支持乱序执行:

  • 重命名映射表(RAT):维护逻辑寄存器到物理寄存器的映射关系。

  • 发射队列(Issue Queue,IQ):缓存等待操作数的指令,并在操作数就绪时将其发射到FU。

  • 重排序缓冲区(Reorder Buffer,ROB):按程序顺序记录所有飞行中的指令,用于顺序提交和异常恢复。

  • Store Buffer(SB):暂存store指令的结果,直到该指令从ROB退休后才写入D-Cache。

  • 物理寄存器文件(PRF):存储所有飞行中指令的结果和已提交但尚未被覆盖的旧值。

  • 旁路网络(Bypass Network):将执行完毕的结果直接前递给等待该结果的指令,无需等待写回寄存器文件。

这些硬件的面积和功耗开销是巨大的。在一个6-wide的乱序核心中,ROB、发射队列、PRF和旁路网络的面积之和可以占到核心面积的30%以上。这也是为什么效率核(如ARM Cortex-A510)采用顺序设计——在面积和功耗受限的场景下,乱序执行的收益不足以弥补其硬件开销。

VLIW与超标量的区别

超长指令字(VLIW)是另一种每周期可以执行多条操作的处理器架构。其核心区别在于并行调度的责任归属:超标量处理器靠硬件动态调度,VLIW靠编译器静态调度。VLIW的优点是硬件简单(无需复杂的乱序执行机制),缺点是代码兼容性差(不同微架构的VLIW处理器需要重新编译)且编译器难以像硬件那样利用运行时信息进行动态优化。VLIW在DSP和部分GPU中有应用,但在通用处理器领域已经基本被超标量取代。Intel的Itanium处理器是VLIW(准确说是EPIC架构)在通用领域的最后一次尝试,最终因性能和生态问题失败退出市场。

本书专注于Superscalar处理器的设计,不讨论VLIW架构。本书将按照一条指令从I-Cache中取出到最终退休的轨迹来组织内容:

  • 第二篇讲述Cache与存储层次,包括I-Cache和D-Cache的设计、预取技术、Cache一致性协议、虚拟存储器和TLB。

  • 第三篇讲述分支预测,从经典的两位计数器到现代的TAGE-SC-L预测器,以及BTB、RAS等目标地址预测机制。

  • 第四篇讲述指令集体系(RISC-V、ARM、x86)和指令解码,包括微操作缓存和指令融合等现代前端优化技术。

  • 第五篇是本书的核心——乱序执行引擎,涵盖寄存器重命名、发射队列、功能单元(整数/浮点/SIMD/向量)、旁路网络、Cluster结构、存储器指令加速、内存一致性模型、ROB和提交。

  • 第六篇介绍真实世界的处理器案例:Alpha 21264、Intel Core、AMD Zen、ARM Cortex-X和RISC-V香山。

  • 第七篇讨论多核与存储系统:SMT、大小核异构、片上互连、内存控制器与DRAM接口。

  • 第八篇介绍先进处理器发展:功耗管理、安全架构、Chiplet、CXL、AI加速器。

设计提示

前向桥接。本章概览了超标量处理器的全貌——从性能公式到流水线组织,从指令相关性到乱序执行的基本原理。但一个关键问题悬而未决:流水线应该做多宽、做多深?这个看似简单的问题背后涉及物理约束、分支预测精度、功耗预算等多重因素的复杂博弈。第 2.0 章将给出数学上的分析框架,用定量模型解释为什么现代处理器的流水线宽度收敛到了6\sim10-wide、深度收敛到了15\sim22级。

正文与图片:CC BY-NC-SA 4.0 · 本仓库少量站点配置代码:MIT