同样标着 3.0 GHz 的 CPU,执行同样的程序,耗时为什么可能差很多?频率只告诉我们时钟走得多快,没有告诉我们每拍完成多少工作。现代 CPU 会尝试让多条指令并行推进,也会越过暂时等不到操作数的指令,先做后面已经准备好的工作。本课沿着指令的依赖关系,理解这些能力的收益与边界。讨论对象是单个核心上的指令流;增加核心、线程或服务器数量,是另外的并行层次。
1 超标量要挖的指令级并行
先看服务器中的普通任务:CPU 从内存读取数据,做加法和比较,再把结果交给存储或网卡。程序在文本中按行排列,编译成机器指令后也有先后顺序,但这不表示每条指令都必须等前面全部结束。计算数组中不同位置的校验值时,后面的加法可能根本不需要前面的结果。可以同时推进的这些工作,就是指令级并行,通常缩写为 ILP。
判断并行性,要看“这条指令需要谁的结果”。假设已经把数值放在寄存器中,a=b+c 与 d=e+f 使用不同的输入,彼此独立;a=b+c 后接 d=a+f,后者必须等前者产生 a。寄存器是核心内部保存操作数的小型存储位置,读取它们与访问主板上的内存条不是同一种代价。为了先看清依赖,本节暂时假设输入已经就绪,也没有缓存未命中。
普通流水线把取指、译码、执行等过程重叠起来,好比不同零件分别处在不同工位。超标量进一步允许每拍在某些阶段处理多条指令,并配置可并行工作的执行资源。它不要求软件显式启动多个线程,也不保证每拍都能完成多条指令。只有程序提供足够独立工作,前端能够供应指令,执行单元能够接收操作,数据也及时到达时,硬件的并行能力才有机会发挥。
用 IPC 表示每时钟周期退休的指令数。这里的退休,是指指令执行完成后,按照程序顺序被确认为有效结果。若在选定测量范围内,单个线程退休了 12000000 条指令,经历 8000000 个核心周期,IPC 就是 1.5。这个平均值可以大于 1,也可以小于 1。它不是 CPU 的固定属性,换一段程序、换一种输入规模,甚至让同一段代码多出缓存未命中,都可能改变它。
因此,看到 4 条指令宽的设计,只能先把它理解为某个环节的容量上限。程序若只有 1 条持续串联的依赖链,就像每个零件都必须等上个零件加工完,空着的工位不能提前制造所需结果。学习超标量,首先要学会在指令之间画箭头,而不是先数核心有多少执行单元。后面的算例会把“能够并行”与“实际做到了并行”分开计算。

2 发射宽度与并行执行资源
本课把“发射”约定为:调度器把操作交给执行单元开始处理。不同资料有时把更早的分派阶段也称为发射,所以读规格前应先确认术语。前端每拍能取回或译码多少指令,分派每拍能把多少操作送入后端,调度器每拍能发射多少操作,退休端每拍能确认多少结果,可能是不同的数字。不能用其中的最大值概括整颗核心。
另一个容易混淆的地方是计数对象。软件看到的是体系结构指令,硬件内部可能把指令拆成微操作,也可能对部分操作采用融合处理。某项资源标称每拍处理 4 个微操作,并不等价于所有程序都能达到 4 IPC。本课的玩具模型令 1 条指令对应 1 个操作,便于算数;迁移到真实芯片时,必须重新核对该资源计数的单位,不能把模型的数字直接写成产品结论。
假设某核心最多每拍发射 4 个操作,具有 2 个整数加法通路、1 个乘法通路,以及 2 个加载通路。这些只是教学配置。如果当前准备好的都是整数加法,它每拍最多利用 2 个加法通路,其余发射名额不能凭空变成加法器。如果有加法、乘法、加载混合出现,也要确认它们是否共享某些端口或地址计算资源。执行单元的数量与可同时使用的通路数量并不总是相等。
还要区分延迟与吞吐能力。延迟描述某个结果要等多久;接收间隔描述通路多久能再接收新操作。某乘法通路的结果延迟是 3 拍,但接收间隔是 1 拍,表示它可以在连续的周期接收互不依赖的乘法。第 1 个结果仍要等到第 3 拍末。若连续乘法都依赖前次结果,这个流水化通路就没有足够输入来维持每拍接收的节奏。
加载也遵循相似约束,但延迟更容易变化。数据若命中核心附近的缓存,可能很快返回;若必须访问更远的缓存或 DDR 内存,就要等更久。不能把教学中设定的 4 拍加载延迟解释为所有芯片的固定指标。内存控制器、通道数量、访问局部性和其他核心造成的竞争,都会影响实际等待。存储设备经 PCIe 等链路提交的数据,通常也要先进入内存,再由 CPU 读取,并不是每条加载指令直接访问固态硬盘。
所以,读性能规格时最好写成有条件的陈述:“在独立整数加法充足、输入已在寄存器、前端可持续供应的条件下,受 2 个加法通路限制,吞吐上限为每拍 2 个加法。”这个说法明确了对象、输入和约束,遇到其他程序时也知道该修改哪项假设。
3 乱序执行与重排序缓冲直觉
顺序执行若遇到暂时拿不到数据的加载,后面已经就绪的加法也可能排队等待。乱序执行允许核心在有限范围内寻找这样的独立工作,先把它完成。它改变的是内部执行次序,程序对外呈现的正确结果仍须符合指令集要求。对于普通寄存器计算,可以先用“提前干活,按序确认”建立直觉;多核心内存可见性的细节,需要以后单独学习。
重排序缓冲通常缩写为 ROB。可以把它理解为按程序顺序登记指令状态的记录表:哪些已经完成,哪些仍在等待,哪些发生异常。指令即使先完成,也不能越过队头未完成的指令直接退休。实际芯片还需要其他结构保存操作数、物理寄存器映射等信息,ROB 并不是把所有事情都装进去的万能仓库。本课只借它理解顺序确认与有限容量。
看下面 6 条教学指令。输入地址和未列出的寄存器均已就绪,内存加载无异常;允许每拍发射 2 条,并提供彼此独立的加载、加法和乘法通路。加法延迟为 1 拍,乘法为 3 拍,加载为 4 拍。结果在周期末可用,依赖它的指令最早在下个周期发射。为隔离调度过程,假定这 6 条指令在第 1 拍前均已进入足够大的窗口。
| 顺序 | 指令含义 | 真正需要等待的前序结果 |
|---|---|---|
| I1 | 从地址 p 加载到 r1 | 无 |
| I2 | r2 = r1 + 1 | I1 |
| I3 | r3 = a + b | 无 |
| I4 | r4 = r3 × c | I3 |
| I5 | r5 = d + e | 无 |
| I6 | r6 = r2 + r4 | I2、I4 |
第 1 拍发射 I1 和 I3,第 2 拍发射 I4 和 I5。I2 仍等着 I1,但并没有阻止 I3、I4、I5 工作。第 4 拍末,I1 与 I4 都完成;第 5 拍发射 I2,第 6 拍才可发射 I6。最后结果在第 6 拍末出现。乱序调度填上了部分等待空隙,却没有消除 I1→I2→I6 这条链上的真实等待。
若另设每拍最多退休 2 条,完成后最早下拍退休,则第 5 拍退休 I1,第 6 拍退休 I2、I3,第 7 拍退休 I4、I5,第 8 拍退休 I6。I5 虽然在第 2 拍末完成,仍要等待更早指令确认。这解释了为什么“结果已算出”“某条指令已完成”和“程序已有多少退休指令”不是同一种进度。
若 I1 最终报告访问异常,核心不能把 I3、I4、I5 的提前计算当作已提交的程序结果。它在合适的程序边界报告异常,并取消更年轻指令的未提交工作,使软件看到可解释的执行状态。这里先不展开恢复结构。只需记住,乱序需要记录、排序和恢复能力,它用这些成本换取等待期间的并行机会,并不是随意更改程序行为。

4 动手:对比理想 IPC 与瓶颈计数
先做纸上实验,再考虑真实机器。规定每条指令对应 1 个操作,没有取指和分派开销,也没有循环分支;所有指令从第 1 拍起都已在窗口内。每拍发射上限为 2,有 2 个加法通路,加法延迟为 1 拍,输入均已就绪。计时从第 1 拍开始,到最后结果产生的周期末结束。这里计算的是这段操作的完成吞吐,暂不计退休尾部,因此不要直接把它标为硬件计数器 IPC。
准备 8 条互不依赖的加法,每拍发射 2 条,最后结果在第 4 拍末产生,完成吞吐为每拍 2 条。把它们改成连续更新同个累加值,后条必须等前条的结果,便要 8 拍,完成吞吐降为每拍 1 条。指令数、发射宽度和加法通路都没变,改变的只是依赖图。这是本课最值得亲手画出的对照。
再把 8 条独立加法放回去,但只保留 1 个加法通路,即使发射宽度仍为 2,也要 8 拍。若恢复 2 个加法通路,却令前端每拍只能送入 1 条新指令,且从空窗口开始,持续执行同类代码时也只能接近每拍 1 条。注意这个前端实验改变了原先“全部已在窗口”的假设,比较时必须明确写出,否则会把模型边界混在一起。
| 条件 | 8 条加法的结果完成周期 | 首要约束 |
|---|---|---|
| 独立,2 路加法,全部已入窗口 | 4 | 发射宽度与加法通路 |
| 串行依赖,2 路加法,全部已入窗口 | 8 | 结果依赖 |
| 独立,1 路加法,全部已入窗口 | 8 | 执行资源 |
| 独立,2 路加法,前端每拍供应 1 条,从空窗口开始 | 至少 8 | 前端供应,另有未建模的流水延迟 |
真实机器实验可以选择连续数组求和,比较 1 个累加器和 4 个独立累加器,最后再合并部分和。使用无符号整数可以让回绕规则明确;若改用浮点数,调整求和顺序可能改变舍入结果,不能只检查速度。工作集先选择能放入被测核心缓存的大小,再扩大到明显超过末级缓存的规模。这样分别观察依赖影响和内存影响,避免同时改变太多条件。
编译器可能自动展开循环、使用向量指令,甚至删除没有被使用的计算。因此应消费最终校验值,并查看反汇编,确认实际累加器数量、加载位置、向量化情况与循环分支。若要比较标量依赖链,可以按编译器文档暂时关闭向量化,同时报告编译器版本和优化选项。源代码看起来有 4 个变量,并不足以证明机器码保留了 4 条独立链。
在支持相关事件的 Linux 机器上,可把下面命令作为测量入口。它假设已有自行编译、校验结果的 ilp_demo,并非本课附带的可执行文件;2 表示示例逻辑 CPU 编号,应换成当前机器可用的编号。
taskset -c 2 perf stat -r 5 -e cycles,instructions,cache-misses ./ilp_demo
固定逻辑 CPU、预热数据、重复 5 次,并记录是否有同核心的其他线程竞争。用退休指令数除以周期数得到测量 IPC,同时报告任务耗时和结果校验。通用 cache-misses 的具体含义依平台而变,不能直接当作 DDR 请求数;前端停顿、后端等待等事件更需查阅该 CPU 的事件定义。若系统没有权限开放性能计数器,仍可比较耗时与汇编,但应明确少了哪类证据。

5 寄存器重命名解决什么
刚才用不同寄存器名表达独立结果,但软件能看到的寄存器名称数量有限,编译器会重复使用它们。名称复用可能造成看起来像依赖、实际上只是保存位置冲突的关系。寄存器重命名让硬件把程序可见的寄存器名,映射到更多内部物理寄存器,使不同版本的值能够暂时并存。这是支持乱序的重要条件,但它不能制造尚未算出的真实输入。
先看真正的读后写依赖:r1=a+b 之后执行 r2=r1+c,第 2 条读取第 1 条生成的值,无论把名字改成什么,都得等加法完成。再看另一种情形:较早的指令还要读取旧的 r1,较晚的独立指令准备写入新的 r1。如果只保留同个保存位置,晚写可能覆盖旧值。通过重命名,旧指令继续读取旧的物理寄存器,新写入使用另个物理寄存器,就能避免这类名字冲突。
例如,程序顺序为 r2=r1+a、r1=b+c、r3=r1+d。第 1 条需要旧 r1,第 3 条需要第 2 条产生的新 r1。假定旧值位于 P7,第 2 条可以分配新位置 P12,第 1 条的输入仍指向 P7,第 3 条的输入则指向 P12。这样第 2 条不必为了保留旧名字而等待第 1 条读完,但第 3 条仍必须等待 P12 的数值准备好。
教材中常把名字冲突称为反依赖和输出依赖,分别对应较早读、较晚写,以及较早写、较晚又写。入门阶段不必背缩写,能回答“这次读取究竟需要哪个版本”就够了。重命名消除的是存储名称带来的限制,真实数据依赖仍在。物理寄存器也有数量限制,待确认的结果越来越多时,新指令可能因为没有空闲位置而无法继续进入窗口。
寄存器重命名也不等于内存地址自动独立。对于 *p 与 *q,必须考虑它们是否指向同个位置;地址尚未算出时,硬件不能仅凭变量名不同就认定无关。实际核心用专门机制处理加载与存储的依赖,本课暂不展开。遇到内存密集代码,应先区分寄存器依赖、可能的地址冲突和数据返回慢,避免把所有停顿都归到重命名能力不足。

6 常见坑:忽视前端/内存瓶颈只谈宽度
最常见的误判,是看到 IPC 较低,就断言应该选择更宽的核心。宽度只有在其他环节能持续供应时才是紧约束。前端若因为指令缓存未命中、译码供给不足,或者控制流方向尚未明确而断流,后端的宽执行资源也可能空闲。后端若一直等待数据,继续增加前端供应还可能更早填满有限的窗口,不能让缺失的数据更快从内存回来。
设某次较远层级的加载在教学时间线上要等 80 拍,后面的 10 条独立操作在几拍内就全部执行完了。乱序确实利用了其中的空隙,但剩余等待仍然存在。若队头加载迟迟不能退休,年轻指令逐渐占满 ROB,核心最终无法继续接纳更多工作。把缓冲容量想成无限大,会高估乱序能遮住的延迟;把延迟想成固定值,又会漏掉多核心竞争和内存排队。
内存问题还要区分延迟与带宽。沿链表追踪下个地址时,只有拿到当前节点,才知道下一次该读哪里,即使服务器装有多个 DDR5 通道,也可能因缺少可并行请求而难以利用总带宽。连续扫描大数组则可能同时产生很多请求,最终受通道持续传输能力限制。2 类程序都可能表现为低 IPC,却分别需要减少依赖访问与减少数据传输,扩大执行宽度对它们的帮助都有限。
把视角放到整机还会出现其他上限。假设服务器配有 25 Gb/s 网卡,其链路位速率折合为 3.125 GB/s,尚未扣除协议开销;若接收处理最终受网卡入口速率限制,提高 CPU 峰值 IPC 不会让链路凭空更快。PCIe 设备还受代际、协商通道数和共享上行链路约束。单条 CPU 加法的并行能力,与外设每秒实际送来多少字节,是不同层级的问题。
频率和功耗也会扰乱比较。相同核心运行不同指令组合,持续频率可能不同;小型主机的散热、机架服务器的功率上限、后台任务竞争,都可能改变耗时。只比较 IPC 而不记录周期和运行时间,可能把指令数增加后的高 IPC 误当作业务变快。对同样的输入,应优先确认结果一致、总耗时下降,再用 IPC 和其他计数解释原因。
因此,排查可以从症状逐步缩小范围:确认实际指令流,检查依赖链,观察前端是否供应不足,再判断缓存、内存或执行资源是否限制推进。这个顺序不是所有机器都适用的固定诊断公式,而是防止从单个数字跳到结论的检查表。若已有明确的内存等待证据,就先围绕数据访问设计对照;没有必要为了证明“核心够宽”而重复测量无关的整数加法。

7 案例:依赖链限制超标量收益
设想 1 台 1U 服务器,配有单路 CPU、2 个 DDR5 内存通道、经 PCIe 连接的 NVMe 存储和 25 Gb/s 网卡。它要为内存中已有的数据块计算整数校验值。请求在进入计算前已经完成读取,本案例测量范围只包含校验循环;存储和网络规格用于说明机器形态,并不自动成为这个循环的性能瓶颈。先把测试数组控制在缓存可容纳的范围,尽量减少远端数据访问的干扰。
最初代码用 sum += x[i] 顺次累加。即使每次加载的数据能及时到达,下次加法也需要上次的 sum。在每拍最多发射 4 个操作、具有 2 个加法通路、加法延迟为 1 拍的模型中,这条加法链仍只能每拍推进 1 次。实际循环还包含加载、地址更新和控制流,因此整个程序的 IPC 可能大于 1;不能因为只有 1 条累加链,就断言整段循环 IPC 必为 1。
把循环改为 4 个无符号累加器,分别处理相邻的不同元素,循环结束后再合并。这样每条链内部仍串行,但不同链可以交错推进。在其他资源充足的教学配置下,2 个加法通路有机会同时接收来自不同链的加法。若有 16 个元素,单链的 16 次主加法至少需要 16 拍;4 条链分担的主加法按资源下界需要至少 8 拍,此外还要计入最终合并、加载及循环管理成本。
这个下界不是“程序必定快 2 倍”的承诺。合并 4 个部分和需要 3 次加法,可以组织成先两两相加、再合并的结构;它有额外延迟与指令开销。寄存器压力也会上升,编译器若不得不把中间值写回栈内存,收益可能被新增访存抵消。数组很短时,准备与收尾的代价占比更大;长循环才更容易接近稳定吞吐。真实结论必须回到反汇编和时间测量。
接着把数组扩大到超过末级缓存,累计读取 1 GiB 数据。此时多个累加器仍能减少计算依赖,却不保证内存持续提供足够字节。以每元素 8 字节为例,若实际可用读取带宽为 20 GB/s,仅数据传输就约需 53.7 ms;这里 GB/s 采用十进制单位,GiB 采用二进制单位,且带宽数字只是场景假设。计算模型低于这个时间,也不能突破数据供应的限制。
更换为追踪指针的节点校验,情况又会变化。下个节点地址依赖当前节点内容,单纯设置 4 个局部累加变量,并没有产生 4 条独立的加载链。只有工作本身提供多个独立遍历,或数据布局允许批量访问,才可能让更多内存请求重叠。观察变量数量并不能替代分析依赖图;真正要问的是“下次访问的地址是否已经知道”。
这个案例留下的可迁移结论是:先说明计时范围,再寻找可独立推进的工作,并确认改写保持结果语义。缓存内求和、超缓存扫描、依赖式追踪虽然都在同台服务器运行,限制却不同。选 CPU、调代码或估计容量时,应分别保留这些场景,不能用缓存内的最佳结果代表整机所有数据处理任务。

8 把 ILP 假设写进性能模型
性能模型的价值,不是给出看似精确的 IPC,而是把结论依赖的条件写清。设测量范围内退休指令数为 N,平均 IPC 为 I,平均有效核心频率为 f,则 CPU 执行时间可近似写为 T = N / (I × f)。频率以每秒周期数计。若指令数为 12000000、IPC 为 1.5、频率为 3.0 GHz,得到约 2.67 ms。它描述所测 CPU 执行范围,不自动包含排队、设备等待和网络传输。
估计 I 时,可以先画出几类上限:前端供应、发射与退休容量、所需执行通路的吞吐、真实依赖链,以及数据供应。它们的单位要统一,不能把“每拍加载 2 次”和“每拍退休 4 条”直接取最小值。应该先说明循环每轮有多少加载、多少加法、多少总指令,把各类资源容量换算成每轮所需周期,再比较这些周期下界。
例如教学循环每轮有 8 个操作,其中 4 个是加法、2 个是加载,其余是已建模的控制与地址操作。假设发射宽度为 4,则发射需求下界为 2 拍;2 个加法通路给出 2 拍下界;每拍只能接收 1 次加载则给出 2 拍下界。若循环携带的依赖链要求每轮至少 3 拍,那么即使忽略其他问题,平均也不可能超过每 3 拍完成 1 轮,即 8/3 个操作每拍。
这里应取各下界的最大值,而不是把所有周期机械相加,因为不同资源的工作可能重叠。但最大值仍只是乐观下界;端口冲突、窗口容量、流水线起止和缓存未命中可能让实际更慢。若真实指令会拆成多个微操作,也要先把内部资源需求折算正确。用这个框架的目的,是定位哪里值得进一步测量,不是替代完整的微架构模拟。
还可以用“吞吐需要足够在途工作”检验假设。假设加载结果需要 4 拍,而目标是平均每拍启动 2 次互不依赖的加载,稳定状态下至少要有约 8 次加载在途中。若算法每次只知道下个地址,根本无法准备这些独立请求。实际需要的窗口还要容纳其他指令,也可能受加载队列等资源限制,不能把这 8 次简单等同于 ROB 必须只有 8 项。
做方案时,为关键场景写出基线、乐观和保守条件,比只写“采用 4 发射 CPU”更有用。例如基线来自当前机器重复测量,乐观条件假设缓存命中且有足够独立链,保守条件增加远端数据访问或同核心竞争。若换机后耗时超出范围,就检查是哪项假设失效。模型中每个数字都应能追溯到题设、公开资源定义或测量结果,不能把教学假设包装成实测性能。

9 从乱序走向分支预测(H21)
到这里,我们一直假设核心知道后面有哪些指令可以进入窗口。真实程序会遇到条件分支,例如检查当前数据块是否损坏,再决定继续扫描还是跳到错误处理。分支结果也需要计算;如果前端总等到条件完全确定才继续取指,宽执行资源很容易缺少工作。乱序只能从已经看到的指令中挑选,无法单靠调度回答“下一段程序到底在哪里”。
分支预测尝试提前选择后续控制流,让前端继续供应可能会用到的指令。预测正确时,后端较容易获得足够独立工作;预测错误时,错误路径上的未提交工作必须取消,再从正确位置继续。于是,超标量扩大每拍处理能力,乱序在依赖允许的范围内调整执行次序,分支预测帮助维持指令来源。它们解决不同问题,又会共同影响最终 IPC。
提前计算仍然要遵守正确性边界。前面介绍的按序退休与恢复机制,使 CPU 可以先做尚未确定有效的工作,再决定是否确认结果。如果最终走向错误路径,已完成也不等于已退休。本课不展开预测器结构、具体恢复时序和推测执行的安全问题;这些内容需要更完整的控制流与状态模型,留到 H21 及后续课程。
也不要把“分支很多”等同于“预测一定很差”。长期方向稳定的分支,与由输入决定、难以预测的分支,可能给前端造成不同影响。循环写成无分支形式也可能增加指令数、依赖或访存。要判断改写是否有效,依然需要同时看结果语义、实际机器码、任务耗时和相关事件,不能因为少了跳转就直接宣布更快。
学习 H21 前,可以用本课的视角重新描述问题:前端是否持续供应,窗口里是否有可执行工作,操作数是否准备好,执行通路是否可用,最早的未退休指令在等什么。若回答是“正确的下一条指令还不确定”,就到达了分支预测的入口;若回答是“下个地址必须等待当前加载”,主要问题仍是数据依赖。把这两种等待分清,后面的推测执行就更容易理解。

放进方案里
在服务器或边缘主机方案中,把“超标量与乱序”写成有边界的计算能力说明。先定义代表性负载:例如 1U 单路服务器处理已经进入内存的 8 字节整数数组,分别测缓存内校验、超缓存连续扫描和指针追踪。注明计时是否包含 NVMe 读取、网卡接收与排队。若总体目标是端到端吞吐,还要把这些阶段纳入预算,避免拿纯计算循环的结果代替整条处理链。
硬件条件记录核心与线程配置、持续频率、内存通道及容量、PCIe 设备协商链路和散热功率条件。宽度规格需注明属于译码、微操作发射还是退休,执行资源需对应实际指令类别。对没有可靠资料的内部参数保留未知,不根据宣传中的最大数字反推所有程序的 IPC。内存条数量也不能直接等同于有效通道数,应按主板插槽规则确认配置。
软件条件写明编译器、优化选项、实际累加器数量、向量化情况与结果校验方式。建立 1 条依赖链和多条独立链的对照,并在相同输入与计时范围下重复测量。记录耗时、退休指令、周期及平台定义明确的缓存或停顿事件。若采用浮点重排,先确认允许的误差;若改变数据布局,同时记录内存占用和维护成本。
模型中分别列出指令供应、执行通路、依赖链与数据带宽的限制,并给出乐观与保守范围。验收以任务完成时间或有效数据吞吐为准,IPC 用来解释结果。若多累加器在缓存内有效、扩大数据后收益消失,就优先检查数据供应;若前端长期断流,则检查代码与控制流。只有证据表明执行容量成为限制时,才把增加宽度写成预期收益来源,并保留重新测量的条件。
10 小结:下一步分支预测与推测执行(H21)
超标量提供每拍处理多条操作的机会,ILP 决定程序能提供多少独立工作。乱序在有限窗口里先执行就绪操作,按序退休维持正确的程序状态;寄存器重命名解除名字冲突,真实数据依赖仍必须等待。理解性能时,要把宽度、通路吞吐、结果延迟、窗口容量和数据供应放在同张图里。
可以用 3 个问题自测:为什么 4 发射核心可能每拍只能推进 1 次累加?为什么较晚的加法完成后还不能退休?为什么大数组扫描的低 IPC 不能直接证明加法器不足?若能分别回答依赖链、顺序确认和数据供应,就已掌握本课主线。
H21 将接着讨论:分支结果尚未确定时,CPU 如何继续获取可能有用的指令;预测错误后,提前做过的工作又怎样被取消。把“先算出来”和“最终确认有效”分开,正是进入分支预测与推测执行的关键。