Files
final-exam/计算机系统结构/重点复习/小题3-控制冲突的解决措施.md

152 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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-流水寄存器的作用]]