6.8 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-06-16 21:21 |
小题 3 — 控制冲突的解决措施
概述
本题考查流水线中控制冲突(Control Hazard)的概念,以及为减少分支指令引起的延迟所采取的有效措施。控制冲突是流水线三大冲突之一,理解其解决方案对理解现代处理器设计至关重要。
[!tip] 考试重点 重点掌握三大类解决措施:暂停、预测、延迟分支。其中延迟分支的"延迟槽"概念和分支预测的基本原理是常考知识点。
正文
一、什么是控制冲突?
控制冲突(Control Hazard)是由分支指令(如 if-else、循环跳转)引起的。当处理器执行分支指令时,需要等到分支结果确定后才能知道下一条应该取哪里的指令。但在等待期间,流水线已经预取了后续指令——如果预测错误,这些预取的指令就必须作废。
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%。如果每条分支指令都暂停 23 拍,流水线效率将下降 30%~75%。这就是为什么必须采用预测技术。
3.2 分支预测(Branch Prediction)
在分支结果确定之前,预测分支方向并提前取指。预测分为两大类:
| 预测类型 | 方法 | 预测准确率 | 硬件复杂度 |
|---|---|---|---|
| 静态预测 | 编译时确定,运行时不变 | 60%~70% | 低 |
| 动态预测 | 运行时根据历史信息动态调整 | 90%~95% | 高 |
静态预测的常见策略:
| 策略 | 预测行为 | 适用场景 |
|---|---|---|
| 总是预测不跳转 | 分支不发生,继续顺序取指 | 循环体外的分支 |
| 总是预测跳转 | 分支发生,跳转到目标地址 | 循环体内的分支(循环通常继续) |
| 根据分支方向预测 | 向后跳转预测为跳转(循环),向前跳转预测为不跳转 | 通用场景 |
| 由编译器标注 | 编译器在分支指令中设置预测位 | Profile-guided 优化 |
动态预测的常见策略:
| 策略 | 原理 | 状态数 |
|---|---|---|
| 1 位预测器 | 记录上次分支结果 | 2 |
| 2 位饱和计数器 | 需要连续 2 次预测错误才改变方向 | 4 |
| 相关预测器 | 参考其他分支的历史 | 4+ |
| 锦标赛预测器 | 多个预测器投票 | 多 |
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 条指令的空间)
- 对于深度流水线(延迟 2~3 拍),延迟分支力不从心
- 需要编译器配合,增加了编译器设计的复杂度
因此现代深度流水线处理器主要依靠动态分支预测,延迟分支主要用于简单 RISC 处理器。
四、三种措施的对比
| 对比维度 | 暂停 | 分支预测 | 延迟分支 |
|---|---|---|---|
| 性能 | 最差 | 最好 | 中等 |
| 硬件开销 | 无 | 中~高 | 低 |
| 需要编译器支持 | 否 | 否 | 是 |
| 适用流水线深度 | 浅 | 深/浅均可 | 浅(1~2 拍延迟) |
| 预测错误时的处理 | 不需要 | 需要冲刷流水线 | 不需要 |
| 现代处理器使用 | 仅作后备 | 主要手段 | 辅助手段 |
[!abstract]- 答案 为减少分支指令引起的控制冲突延迟,采取的有效措施包括:
(1)暂停:等待分支结果确定后再取指,简单但性能最差。
(2)分支预测:静态预测(预测不跳转/预测跳转)和动态预测(1 位/2 位饱和计数器、相关预测器)。
(3)延迟分支:编译器在分支指令后的延迟槽中填入必定执行的指令。