想象一家繁忙的餐厅:收银台按顾客下单顺序记好单子,后厨里好几位厨师同时开工。聪明的厨师不会傻等“炖牛腩”慢慢入味才去做下一道,他会先炒别的菜;但出菜口必须严格按顾客下单的顺序上桌,否则顾客会投诉“我点的菜怎么还没来”。超标量乱序执行与重排序缓冲(Reorder Buffer, ROB)的配合,正是这幅画面在处理器里的复刻:指令可以乱序执行,但提交给外部世界的结果必须按程序原始顺序完成。这一机制既提高了硬件利用率,又保证了程序语义和异常处理的精确性。

岗位关联

对 AI 从业者而言,理解超标量与 ROB,是看懂 CPU 性能指标、做推理/训练部署选型、优化算子时“瓶颈到底在哪”的关键底层认知。

目录
  1. 可视化演示
  2. 超标量基础
  3. 乱序执行动机
  4. 动态调度机制
  5. 寄存器重命名
  6. ROB原理
  7. 协作流程
  8. 资源约束
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态展示最近最少使用(LRU)页面置换算法的执行过程,展示页面命中、缺页与置换操作,支持帧数调节。

本节配有交互动效(进入视口后自动加载,约 3KB)。

LRU页面置换 访问页面时若已在内存中则命中,否则产生缺页中断 缺页时淘汰最近最久未使用的页面,载入新页面 命中率 = 命中次数 / 总访问次数 LRU基于时间局部性原理

超标量基础

一家小餐馆只有一个厨师,必须按订单顺序一碟一碟做菜。遇到炖牛腩要等四十分钟,后面所有菜都跟着干等。老板后来请了三位厨师,可以同时开三个灶台:炖牛腩占一个锅,另外两个锅还能继续炒别的菜。这种“请多位厨师同时开工”的硬件设计,就是超标量(superscalar)处理器的生活原型。

在处理器世界里,指令就是订单,执行单元就是灶台。超标量处理器每个时钟周期可以取回几条指令,并同时发射给多个执行单元;“取指宽度”和“发射宽度”决定了它一次能看几张订单、开几个灶。相比一次只处理一条指令的标量处理器,超标量处理器靠指令级并行(ILP)让每个周期完成的指令数更多。

每周期完成的指令数叫 IPC(Instructions Per Cycle)。如果发射宽度是 W,理想情况下 IPC 上限就是 W。但依赖、结构冲突和资源不足都会让实际值低于这个上限。

请了几位厨师,只是把“同时做菜”的上限拉高;真正能不能把锅全用上,还要看食材、菜谱和协作方式。下面几节,就是在解决这些“后厨协作”问题。

IPCmax⁡=W\displaystyle IPC_{\max}=W
IPC 就像后厨“每单位时间端出几道菜”;发射宽度 W 则是能同时开火的炒锅数,理想情况下上限就是同时开锅的数量。
  1. IPC衡量处理器每个时钟周期平均完成多少条指令,相当后厨单位时间端出的菜量。
    IPC=完成的指令数时钟周期数\displaystyle IPC=\frac{\text{完成的指令数}}{\text{时钟周期数}}
  2. W(发射宽度)每个周期最多能发射的指令数,相当于后厨同时可开火的新炒锅数。
    W=每周期最多发射指令数\displaystyle W=\text{每周期最多发射指令数}
  3. 最大值在理想情况下,发射的指令都能顺利执行,所以 IPC 的上限就是 W。
    IPCmax⁡=W\displaystyle IPC_{\max}=W

乱序执行动机

有了多位厨师,按订单顺序做仍然会让团队卡壳:炖牛腩没熟,后面的菜依然做不了。聪明的后厨开始观察依赖:牛腩炖上之后,先把不需要它的大虾下锅。乱序执行(Out-of-Order Execution)的核心动机,就是不让一条等待数据的指令挡住后面所有独立指令。

如果一条指令要读的寄存器还没被前面的指令写出来,这种关系叫“写后读”(RAW),是真正的数据依赖,绕不开。另两类“读后写”(WAR)和“写后写”(WAW)属于伪依赖,只是寄存器名称重用造成的。顺序执行时,伪依赖也会造成无谓等待。

乱序执行的目标,是把总执行时间从“所有指令时间之和”压缩成“最长依赖链的时间”,也就是关键路径(Critical Path)。关键路径越长,乱序执行的收益越看得见。

乱序不是“乱来”,而是把等待的时间填满。它需要的不是厨师自己拍脑袋,而是一套能感知“哪道菜料齐了”的后厨调度系统。

Too≈Dcrit+1\displaystyle T_{\text{oo}} \approx D_{\text{crit}}+1
后厨总时长不是所有菜的烹饪时间相加,而是那条“必须先等前菜、再等后菜”的最长链路决定的。
  1. T_oo(乱序执行总时间)从第一条指令开始执行到最后一条指令提交所用的总周期数。
    Too=乱序执行总周期数\displaystyle T_{\text{oo}}=\text{乱序执行总周期数}
  2. D_crit(关键路径长度)程序中最长的依赖链所需的周期数,像“炖牛腩→炝锅→收汁”一路等下来的总时长。
    Dcrit=最长依赖链周期数\displaystyle D_{\text{crit}}=\text{最长依赖链周期数}
  3. 加1最后一个结果写回和提交还需要一个额外周期;乱序执行能让总时间逼近关键路径,但很难突破它。
    Too≈Dcrit+1\displaystyle T_{\text{oo}}\approx D_{\text{crit}}+1

动态调度机制

后厨如何决定先做哪道菜?不是老板按单喊,而是每个厨师扫一眼台面上的“配菜盒”——配菜齐了就动手,没齐就放回去等。这就是动态调度:硬件在运行时根据数据是否就绪,动态安排执行顺序。

保留站(Reservation Station)就是那些配菜盒。指令发射时,源操作数要么已经准备好,要么会由后面的执行结果送来;一旦源操作数齐了,这条指令就可以被送到执行单元。结果通过公共数据总线(CDB)广播给所有等待它的保留站,相当于电台喊一声“R1 的值来了”,需要的厨师都听到。

每周期若有 W 个发射槽,硬件就从就绪指令中挑出最多 W 条去执行。“就绪”可以写成:两个源操作数的有效位都为真。

保留站让后厨从“按单排队”变成“按料开工”。但菜名相同会带来端错盘的风险,这就要靠“给托盘编号”的寄存器重命名来解决。

Readyi=Vj∧Vk\displaystyle \text{Ready}_{i}=V_{j}\land V_{k}
就绪标志就像是配菜盒上贴的“料已齐”贴纸;只有两张贴纸都贴上,这道菜才能下锅。
  1. Ready_i第 i 条指令当前是否已经可以执行,等价于厨师判断“这道菜现在能不能开炒”。
    Readyi∈{0,1}\displaystyle \text{Ready}_{i}\in\{0,1\}
  2. V_j 与 V_k两个源操作数的有效位,1 表示数值已经产生并通过总线广播给了保留站。
    Vj=1 表示源操作数1已就绪\displaystyle V_{j}=1\ \text{表示源操作数1已就绪}
  3. 逻辑与(∧)两个源操作数必须同时就绪,缺失任何一个都不能执行,对应“配菜没齐不能下锅”。
    Readyi=Vj∧Vk\displaystyle \text{Ready}_{i}=V_{j}\land V_{k}

寄存器重命名

两个客人都点“番茄炒蛋”,如果只按菜名传菜,很容易端错。后厨干脆给每一份出锅的菜贴独立托盘号:明明都叫番茄炒蛋,13 号托盘和 27 号托盘完全不同。寄存器重命名就是这么做的。

程序员看到的寄存器叫架构寄存器(Architectural Register),相当于菜名;物理寄存器(Physical Register)是实际存放数据的托盘。处理器每次遇到写架构寄存器,就分配一个新的物理寄存器并更新映射表,而不是直接覆盖旧值。这样读旧值的指令还能拿到旧托盘,写新值的指令不用等待旧读完成。

重命名把 WAR 和 WAW 这种“名字吵架”消除了,但 RAW 真依赖依然要等数据本身。每执行一次“写架构寄存器”,分配器就从空闲物理寄存器池中拿出一个新托盘。

托盘编号解决了“同名菜端错”的问题,但后厨还需要一套机制来保证上菜顺序。这就是出菜口的传送带——ROB。

Pnew=alloc⁡()\displaystyle P_{\text{new}}=\operatorname{alloc}()
分配器就是发放托盘号的窗口:每次写入都领新托盘,旧托盘还能让没读完的指令继续使用。
  1. P_new(新物理寄存器)为本次写操作分配的实际存储位置,可以理解为新托盘号。
    Pnew∈{P0,P1,…,PN−1}\displaystyle P_{\text{new}}\in\{P_{0},P_{1},\dots,P_{N-1}\}
  2. alloc()(分配函数)从空闲物理寄存器池中取出一个未使用的寄存器,就像从柜子里拿一个空托盘。
    Pnew=alloc⁡()\displaystyle P_{\text{new}}=\operatorname{alloc}()
  3. 更新映射表把架构寄存器名改成新物理寄存器号,之后的读操作都去新托盘找数据。
    map⁡[Ad]←Pnew\displaystyle \operatorname{map}[A_{d}]\leftarrow P_{\text{new}}

ROB原理

出菜口有一条按订单编号的传送带。不管菜在哪个灶上先做好,都必须放进自己编号的格位;等前面的号都端走,才轮到后面的号。这条传送带就是重排序缓冲(Reorder Buffer, ROB)。

ROB 是一个循环队列,每个条目对应一条已发射指令,按程序原始顺序记录指令的编号、目的寄存器和状态。指令发射时分配一个 ROB 条目,执行完只把状态改成完成,不能立刻修改架构可见状态;只有到达队列头部,并且状态为完成,才真正提交。

循环缓冲的队头向前移动一格,空位就释放给新指令。容量有限,满了就必须暂停取指和发射。

ROB 像传送带一样把“哪道菜先做好”和“哪道菜先上桌”彻底分开。最后我们再看看整个厨房从接单到上桌的完整流程。

head=(head+1) mod N\displaystyle \text{head}=(\text{head}+1)\bmod N
传送带格位从 0 到 N-1 转圈复用;端走一单,头指针就往前走一格,空出来的格位又能放新菜。
  1. head(队头指针)指向下一条应该提交的指令,也就是传送带上“下一个轮到”的格位。
    head=下一条应提交指令的ROB下标\displaystyle \text{head}=\text{下一条应提交指令的ROB下标}
  2. 模 N 循环当 head 走到 N-1 后,再加 1 会回到 0,让 ROB 像转盘一样循环使用。
    (N−1+1) mod N=0\displaystyle (N-1+1)\bmod N=0
  3. 释放空间每次提交,head 前进一格;tail 在发射时前进,二者之间的距离就是 ROB 当前占用空间。
    head=(head+1) mod N\displaystyle \text{head}=(\text{head}+1)\bmod N

协作流程

完整流程是:收银台接单(取指)、把菜单送到配菜台(发射)、厨师开炒(执行)、出锅放上传送带(写回)、传送带按编号端出(提交)。每一环都有分工,出菜口把最后一道关。

乱序处理器的指令生命周期分为取指(Fetch)、发射(Issue)、执行(Execute)、写回(Writeback)、提交(Commit)。写回只是把结果放到物理寄存器或数据缓存里,提交才更新架构寄存器、内存等外部可见状态。提交阶段由 ROB 头部主导,因此整体对外是按序完成。

精确异常(Precise Exception)正是靠这种机制实现:异常发生时,异常指令之前的所有指令已提交,之后的所有指令尚未提交;处理器可以清空后续未提交指令,回到一个与程序边界完全一致的状态。这保证了操作系统能可靠地处理缺页、系统调用和时钟中断。

传送带保证了“再乱的后厨,上桌都有序”。但传送带的格位数是有限的,加太多格子也会带来新的成本与瓶颈。

commit←ROB[head].done\displaystyle \text{commit}\leftarrow \text{ROB}[\text{head}].\text{done}
出菜口只认一个规则:队头的菜做好了,才允许端出去;队头的菜没做好,后面的菜再香也得等着。
  1. ROB[head]取出 ROB 队头条目,它是程序顺序中最早未被提交的指令。
    entry=ROB[head]\displaystyle \text{entry}=\text{ROB}[\text{head}]
  2. done(完成状态)该指令已经执行完并把结果写入了物理寄存器,等价于菜已出锅并放在对应格位。
    entry.done=1\displaystyle \text{entry.done}=1
  3. commit(提交)只有队头完成,才将结果更新到架构状态并释放条目;这就是“顺序提交”。
    commit←ROB[head].done\displaystyle \text{commit}\leftarrow \text{ROB}[\text{head}].\text{done}

资源约束

出菜口只有 N 个格子,如果客人太多,后厨哪怕有十位厨师也只能停在新单后面。ROB 的容量就是乱序窗口的天花板:在飞指令越多,需要的格子越多。

硬件资源之间互相牵连:发射宽度 W 决定每周期最多注入多少条指令,执行延迟 L 决定一条指令要占多久资源。排队论中的小定律(Little's Law)告诉我们,能同时“在途”的指令数约等于注入速率乘平均延迟。因此实际峰值 IPC 受发射宽度和 ROB 容量双重限制。

若 ROB 大小 N 不够,后厨能接的单就被格子数卡死;若执行延迟大,则需要更多格子才能维持同样的吞吐。所以盲目加大发射宽度并不划算,还会带来更大的物理寄存器堆、更多端口和功耗开销。

ROB 容量、发射宽度和执行延迟,像厨房的传送带长度、灶台数量和做菜时间,缺一不可。理解了这些资源约束,也就理解了为什么处理器厂商要在面积、功耗和性能之间反复权衡。

IPCmax⁡=min⁡(W,⌊NL⌋)\displaystyle IPC_{\max}=\min\left(W,\left\lfloor \frac{N}{L}\right\rfloor\right)
想象传送带每轮能走 4 道菜,但每道菜要占 5 个格子;如果总共只有 20 个格子,刚好够用,再少就会拖慢上菜速度。
  1. W(发射宽度)每周期最多能送入后厨的新指令数,是 IPC 的第一个天然上限。
    IPCmax⁡≤W\displaystyle IPC_{\max}\le W
  2. N/L(ROB 容量 ÷ 平均延迟)传送带格子数 N 能容纳的“每周期新单数”,即平均延迟为 L 时能持续消化的发射速率。
    NL=ROB能支撑的理论发射速率\displaystyle \frac{N}{L}=\text{ROB能支撑的理论发射速率}
  3. min 取较小者发射宽度再大,如果 ROB 格子不够,流水线依然会被堵住;最终峰值取决于两者的短板。
    IPCmax⁡=min⁡(W,⌊NL⌋)\displaystyle IPC_{\max}=\min\left(W,\left\lfloor \frac{N}{L}\right\rfloor\right)

AI 应用场景

CPU 性能调优与推理部署选型

阶段:模型部署与性能优化

解决的问题:AI 推理服务常遇到 CPU 单核瓶颈。传统做法只关注主频和核心数,无法解释为什么某些模型在“看起来更快”的 CPU 上反而更慢。理解乱序执行后,可以关注关键路径长度、ROB 容量和发射宽度等指标,针对性优化。

具体案例:性能工程师 profiling 一个 Transformer 推理算子,发现连续乘加指令形成长 RAW 依赖链。通过指令重排和循环展开缩短关键路径,再选择 ROB 更大、发射宽度更高的 CPU,推理延迟明显下降。

深度学习编译器的算子调度

阶段:计算图优化与内核生成

解决的问题:编译器就像“后厨调度员”,需要把算子依赖图画成可并行的执行计划。传统顺序调度浪费空闲计算单元;借鉴乱序执行思想,可以让无依赖算子先执行,再用类似 ROB 的同步机制保证最终结果与拓扑排序一致。

具体案例:TVM/XLA 在生成 GPU kernel 时,分析算子间的数据依赖,把互相独立的矩阵乘法放进不同 CUDA stream 并行执行,并用事件(event)同步保证提交顺序正确。这和保留站等待数据、ROB 顺序提交的思路同构。

操作系统上下文切换与精确中断

阶段:系统软件层与调度

解决的问题:操作系统每毫秒触发一次时钟中断,需要精确知道“当前进程停在哪条指令边界”,才能安全切换进程。如果乱序执行破坏了寄存器状态的一致性,调度器将无法恢复现场。

具体案例:Linux 内核的 schedule() 依赖处理器响应中断后,能准确返回用户态 PC 和寄存器现场。ROB 提供精确异常,确保中断前指令已验证提交、中断后指令尚未提交,从而让 AI 训练/推理进程的上下文切换可靠高效。

Python 演示

python80 行
class Insn:    def __init__(self, tag, dest, srcs, latency):        self.tag = tag        self.dest = dest        self.srcs = srcs        self.latency = latency        self.issue = -1        self.done = -1        self.commit = -1  class OoOSim:    def __init__(self, width=2, rob_size=4):        self.width = width        self.rob_size = rob_size        self.ready = set()        self.executing = []        self.rob = []     def run(self, insns, max_cycle=20):        n = len(insns)        issued_idx = 0        for c in range(max_cycle):            # 按ROB头部顺序提交            while self.rob and self.rob[0].done != -1:                insn = self.rob.pop(0)                insn.commit = c             # 执行单元前进一个周期            progressing = []            for item in self.executing:                item[1] -= 1                if item[1] == 0:                    insn = item[0]                    insn.done = c                    self.ready.add(insn.dest)                else:                    progressing.append(item)            self.executing = progressing             # 按程序顺序发射,最多 width 条            slots = self.width            while slots > 0 and issued_idx < n and len(self.rob) < self.rob_size:                insn = insns[issued_idx]                insn.issue = c                self.rob.append(insn)                if all(src in self.ready for src in insn.srcs):                    self.executing.append([insn, insn.latency])                issued_idx += 1                slots -= 1             # 源操作数就绪的等待指令可以乱序开始执行            for insn in self.rob:                if (insn.done == -1 and insn.issue != -1 and                        all(src in self.ready for src in insn.srcs) and                        not any(item[0] is insn for item in self.executing)):                    self.executing.append([insn, insn.latency])             self.print_state(insns, c)            if issued_idx == n and not self.rob:                break     def print_state(self, insns, c):        parts = ["cycle %d:" % c]        for insn in insns:            parts.append("%s i=%d d=%d c=%d" % (                insn.tag, insn.issue, insn.done, insn.commit))        print("  ".join(parts))        print("ready:", sorted(self.ready),              "rob:", [i.tag for i in self.rob])  if __name__ == "__main__":    insns = [        Insn("I1", "R1", [], 3),        Insn("I2", "R2", ["R1"], 3),        Insn("I3", "R3", ["R1"], 1),        Insn("I4", "R4", ["R2", "R3"], 2),    ]    OoOSim(width=2, rob_size=4).run(insns)
目的:用一个小型模拟器展示四条指令如何在宽度受限、ROB 受限的情况下乱序执行,却仍按程序顺序提交。
  1. if all(src in self.ready for src in insn.srcs):判断源操作数是否都“料齐”,齐了才能把指令送入执行单元。
  2. self.executing.append([insn, insn.latency])送进执行单元并启动延迟倒计时,模拟厨师开始做菜。
  3. self.ready.add(insn.dest)指令写回后把结果加入就绪集合,相当于 CDB 广播结果给等待的指令。
  4. while self.rob and self.rob[0].done != -1:只有 ROB 头部指令完成才提交,出菜口严格按顺序上菜。
  5. insn.commit = c记录提交周期;对比三条指令的 done 和 commit,就能看到“乱序完成,顺序提交”。

常见误区

误区乱序执行是不是像多个线程乱跑一样,处理器想执行哪条就执行哪条,最后结果可能和程序不一样?

正解乱序只能在数据依赖允许的范围内调整执行次序。ROB 会按程序原始顺序提交,外部可见的寄存器写入和内存更新顺序完全符合程序语义。乱序改变的是“什么时候做”,而不是“做出来的结果”。

为何常见:“乱序”这个词太容易让人联想到无序和随机,忽略了硬件内部有严格的依赖检查和提交机制。

误区指令都乱序完成了,发生异常时处理器状态一定乱七八糟,没法恢复到某条指令边界。

正解ROB 就是为了解决这个问题而存在的。异常指令之前的指令已经按序提交,之后的指令尚未提交;处理器可以丢弃后续未提交状态,精确回到异常指令对应的边界。可以说没有 ROB,就没有现代操作系统的可靠中断与进程切换。

为何常见:直觉上“乱序”和“可恢复的精确状态”是矛盾的,但 ROB 在提交环节重新建立了秩序。

误区买 CPU 就看每周期能发射 8 条还是 12 条指令,数字越大越快。

正解发射宽度只是入口宽度。ROB 容量、保留站数量、访存带宽、物理寄存器数和功耗都会成为瓶颈。ROB 一满,发射窗口就会停止;盲目加宽可能导致面积和功耗暴涨,而实际性能提升有限。

为何常见:厂商宣传常强调单一指标,用户容易忽略流水线是“木桶效应”,多个资源必须配套。

误区寄存器重命名之后,指令之间就没有依赖了,可以随便并行。

正解重命名只能消除 WAR 和 WAW 这类由寄存器名称重用造成的伪依赖。RAW 是真依赖,因为后一条指令必须读到前一条指令计算出的数据,重命名无法让数据提前诞生。

为何常见:把“重命名”理解成万能解耦,忽略了“数据必须被算出来”这个物理事实。

误区ROB 是不是像缓存一样,存放指令内容,执行单元从中取指令去执行?

正解ROB 不保存完整的指令操作数和译码结果,它记录的是每条指令的原始顺序、目的寄存器、完成状态和异常信息,用于提交阶段把关。真正等数据、执行的是保留站和执行单元。

为何常见:看到“Buffer”就联想到缓存,但 ROB 的全名强调“Reorder”,核心职责是重排序和提交控制。

面试题

Q1. 超标量处理器的核心特征是什么?

单选题 · easy

  • A.主频更高
  • B.每周期可发射并执行多条指令← 正确答案
  • C.缓存容量更大
  • D.支持多核心

答案:B。超标量强调在一个时钟周期内同时发射多条指令到多个执行单元,就像后厨多位厨师同时开火。主频、缓存和多核心是另一个维度的优化,不是超标量的定义。

Q2. 下列哪一个是必须等待的“真依赖”?

单选题 · easy

  • A.写后读(RAW)← 正确答案
  • B.读后写(WAR)
  • C.写后写(WAW)
  • D.读后读(RAR)

答案:A。RAW 是后一条指令要读前一条指令尚未写出的结果,像“必须等牛腩炖熟才能收汁”。WAR 和 WAW 是伪依赖,可用寄存器重命名消除;RAR 没有依赖。

Q3. “发射宽度”W 指的是什么?

单选题 · easy

  • A.指令缓存的行数
  • B.每个周期最多发射的指令数← 正确答案
  • C.执行单元的流水线级数
  • D.分支预测器的条目数

答案:B。发射宽度 W 是每周期最多能送入后厨的“新订单”数量,决定了理论 IPC 上限。缓存行数、流水线级数和分支预测都与发射宽度无关。

Q4. ROB 中一条指令什么时候可以提交?

单选题 · mid

  • A.执行完成并到达 ROB 头部时← 正确答案
  • B.发射后立即提交
  • C.只要结果写回就立刻提交
  • D.必须等所有指令执行完成

答案:A。ROB 是按程序原始顺序记录指令的队列,只有队头指令执行完成才能提交。即使后续指令已经完成,也必须等待前面的指令先提交,就像出菜口必须按单上菜。

Q5. 当 ROB 满时,处理器通常会怎么做?

单选题 · mid

  • A.继续发射但缓存到内存
  • B.暂停新的取指和发射,等待 ROB 空出← 正确答案
  • C.清空整个 ROB 重新开始
  • D.交给操作系统软件模拟执行

答案:B。ROB 是循环缓冲,容量有限。满时没有新格位给新指令,前端取指和发射必须停顿,否则指令没有地方记录顺序。把指令缓存到内存或清空 ROB 都不符合硬件设计。

Q6. 寄存器重命名主要解决什么问题?

单选题 · mid

  • A.提高缓存命中率
  • B.消除 WAR 和 WAW 伪依赖← 正确答案
  • C.减少分支预测次数
  • D.加速内存访问

答案:B。重命名通过给每次写操作分配新物理寄存器,避免“同名菜端错盘”造成的等待。RAW 真依赖依然存在,缓存和分支预测不是重命名的职责。

Q7. 某处理器发射宽度 W=4,平均执行延迟 L=5。按 Little 定律 N≈W×L,要维持理论峰值 IPC,ROB 至少约需多少个条目?

单选题 · extreme

  • A.4
  • B.8
  • C.16
  • D.20← 正确答案

答案:D。在飞指令数约等于发射速率乘平均延迟,即 4×5=20。若 ROB 少于 20,传送带格子不够,就无法持续维持每周期 4 条指令的提交速率,峰值 IPC 会被 ROB 容量卡住。

Q8. 保留站在乱序执行中承担哪些工作?

多选题 · mid

  • A.等待源操作数就绪← 正确答案
  • B.检测数据就绪后启动执行← 正确答案
  • C.通过公共数据总线广播结果← 正确答案
  • D.按程序顺序提交指令

答案:ABC。保留站像配菜盒,负责等料、触发执行和接收广播;提交顺序由 ROB 控制,不是保留站的职责,因此 D 错。

Q9. 下列哪些依赖可以通过寄存器重命名消除?

多选题 · mid

  • A.写后读(RAW)
  • B.写后写(WAW)← 正确答案
  • C.读后写(WAR)← 正确答案
  • D.读后读(RAR)

答案:BC。WAW 和 WAR 是因为寄存器名称被重复使用造成的伪依赖,换新托盘即可消除。RAW 是数据真依赖,必须等待结果;RAR 不构成依赖。

Q10. 关于乱序执行与 ROB 的关系,下列说法正确的是?

多选题 · hard

  • A.执行单元可以在依赖允许时乱序完成指令← 正确答案
  • B.ROB 保证按程序原始顺序提交指令← 正确答案
  • C.提交前结果不能更新架构寄存器等可见状态← 正确答案
  • D.异常发生后可以继续提交后续指令

答案:ABC。后厨可以乱序做菜,但出菜口必须按单。指令执行和写回物理寄存器可以先乱序发生,提交阶段由 ROB 把关;只有异常指令之前的指令已提交,D 错。