--- tags: - 计算机系统结构 - 重点复习 - 流水线 - 控制冲突 create time: 2026-06-16 21:21 --- # 小题 3 — 控制冲突的解决措施 ## 概述 本题考查流水线中**控制冲突**(Control Hazard)的概念,以及为**减少分支指令引起的延迟**所采取的有效措施。控制冲突是流水线三大冲突之一,理解其解决方案对理解现代处理器设计至关重要。 > [!tip] 考试重点 > 重点掌握三大类解决措施:**暂停**、**预测**、**延迟分支**。其中延迟分支的"延迟槽"概念和分支预测的基本原理是常考知识点。 ## 正文 ### 一、什么是控制冲突? **控制冲突**(Control Hazard)是由**分支指令**(如 if-else、循环跳转)引起的。当处理器执行分支指令时,需要等到分支结果确定后才能知道下一条应该取哪里的指令。但在等待期间,流水线已经预取了后续指令——如果预测错误,这些预取的指令就必须作废。 ```mermaid graph LR A["Branch Instruction"] --> B{"Branch Taken?"} B -- "Yes" --> C["Target Address"] B -- "No" --> D["PC + 4 (Next)"] style B fill:#ff9800 ``` > [!question] 控制冲突的代价有多大? > 假设分支指令在 EX 段(第 3 段)确定结果,那么在 5 段流水线中,分支指令后面有 2 条指令已经被取入流水线。如果分支跳转了,这 2 条指令就要作废——浪费 2 个时钟周期。对于深度流水线(如 15~20 段),分支延迟更大。 ### 二、解决措施总览 | 措施 | 原理 | 性能收益 | 硬件开销 | |------|------|:--------:|:--------:| | **暂停(Stall)** | 等待分支结果确定后再取指 | 无(性能最差) | 无 | | **预测(Predict)** | 猜测分支方向,提前取指 | 高(预测准确时) | 中~高 | | **延迟分支(Delayed Branch)** | 分支指令后的延迟槽总被执行 | 中等 | 低 | ### 三、措施详解 #### 3.1 暂停(Stall) 最简单的方法:分支指令进入流水线后,**暂停**后续指令的取指,直到分支结果确定。 - **优点**:实现最简单,零硬件开销 - **缺点**:性能损失严重。对于 $k$ 段流水线,分支在第 $m$ 段确定结果时,浪费 $m-1$ 个时钟周期 > [!warning] 为什么暂停不可接受? > 分支指令在程序中占比约 15%~25%。如果每条分支指令都暂停 2~3 拍,流水线效率将下降 30%~75%。这就是为什么必须采用预测技术。 #### 3.2 分支预测(Branch Prediction) 在分支结果确定之前,**预测**分支方向并提前取指。预测分为两大类: | 预测类型 | 方法 | 预测准确率 | 硬件复杂度 | |----------|------|:----------:|:----------:| | **静态预测** | 编译时确定,运行时不变 | 60%~70% | 低 | | **动态预测** | 运行时根据历史信息动态调整 | 90%~95% | 高 | **静态预测的常见策略**: | 策略 | 预测行为 | 适用场景 | |------|----------|----------| | 总是预测不跳转 | 分支不发生,继续顺序取指 | 循环体外的分支 | | 总是预测跳转 | 分支发生,跳转到目标地址 | 循环体内的分支(循环通常继续) | | 根据分支方向预测 | 向后跳转预测为跳转(循环),向前跳转预测为不跳转 | 通用场景 | | 由编译器标注 | 编译器在分支指令中设置预测位 | Profile-guided 优化 | **动态预测的常见策略**: | 策略 | 原理 | 状态数 | |------|------|:------:| | 1 位预测器 | 记录上次分支结果 | 2 | | 2 位饱和计数器 | 需要连续 2 次预测错误才改变方向 | 4 | | 相关预测器 | 参考其他分支的历史 | 4+ | | 锦标赛预测器 | 多个预测器投票 | 多 | ```mermaid stateDiagram-v2 [*] --> StronglyNotTaken StronglyNotTaken --> WeaklyNotTaken: "Taken" WeaklyNotTaken --> WeaklyTaken: "Taken" WeaklyTaken --> StronglyTaken: "Taken" StronglyTaken --> WeaklyTaken: "Not Taken" WeaklyTaken --> WeaklyNotTaken: "Not Taken" WeaklyNotTaken --> StronglyNotTaken: "Not Taken" ``` > [!note] 2 位饱和计数器的工作原理 > 2 位饱和计数器是最经典的动态预测方案: > - **4 个状态**:强不跳转(00)、弱不跳转(01)、弱跳转(10)、强跳转(11) > - **预测规则**:高位为 1 预测跳转,高位为 0 预测不跳转 > - **更新规则**:分支跳转时计数器+1(饱和),不跳转时-1(饱和) > - **优势**:偶发的方向改变不会立即改变预测(需要连续 2 次才翻转),适合循环场景 #### 3.3 延迟分支(Delayed Branch) **核心思想**:编译器将分支指令后面的一条指令定义为**延迟槽**(Delay Slot),延迟槽中的指令**无论分支是否跳转都会被执行**。 ``` BEQ R1, R2, TARGET ; 分支指令 ADD R3, R4, R5 ; 延迟槽:总被执行 SUB R6, R7, R8 ; 分支目标(如果跳转) ``` 编译器的任务是找到一条**安全的、有用的**指令填入延迟槽: | 填充策略 | 方法 | 效果 | |----------|------|------| | 从前调度 | 将分支指令**前面**的独立指令移入延迟槽 | 最常用 | | 从目标调度 | 将分支目标处的指令复制到延迟槽 | 适用于循环 | | 从失败路径调度 | 将分支不跳转时的下一条指令移入延迟槽 | 适用于 if-else | | 填充 NOP | 找不到合适指令时填空操作 | 无收益但保证正确性 | > [!question] 思考:延迟分支有什么局限? > 1. 只能补偿**1 拍**的分支延迟(延迟槽只有 1 条指令的空间) > 2. 对于深度流水线(延迟 2~3 拍),延迟分支**力不从心** > 3. 需要编译器配合,增加了编译器设计的复杂度 > > 因此现代深度流水线处理器主要依靠**动态分支预测**,延迟分支主要用于简单 RISC 处理器。 ### 四、三种措施的对比 | 对比维度 | 暂停 | 分支预测 | 延迟分支 | |----------|:----:|:--------:|:--------:| | 性能 | 最差 | 最好 | 中等 | | 硬件开销 | 无 | 中~高 | 低 | | 需要编译器支持 | 否 | 否 | 是 | | 适用流水线深度 | 浅 | 深/浅均可 | 浅(1~2 拍延迟) | | 预测错误时的处理 | 不需要 | 需要冲刷流水线 | 不需要 | | 现代处理器使用 | 仅作后备 | **主要手段** | 辅助手段 | > [!abstract]- 答案 > 为减少分支指令引起的控制冲突延迟,采取的有效措施包括: > > **(1)暂停**:等待分支结果确定后再取指,简单但性能最差。 > > **(2)分支预测**:静态预测(预测不跳转/预测跳转)和动态预测(1 位/2 位饱和计数器、相关预测器)。 > > **(3)延迟分支**:编译器在分支指令后的延迟槽中填入必定执行的指令。 ## 关联笔记 - [[计算机系统结构/复习文档/流水线技术]] - [[小题2-流水线的分类]] - [[小题4-流水寄存器的作用]]