0%
7 min read 学习笔记 学习路线

计算机组成原理 Notes (Part 2): 数据通路与性能

计算机组成原理学习笔记(下):数据通路设计、流水线、分支预测、Cache、总线与计算机性能指标。

这是 计算机组成原理 Notes 系列的第二期。

数据通路设计 (Data Path)

数据通路是 CPU 中执行数据操作的路径,包含以下关键组件:

基本组件

1. 程序计数器 (PC)

  • 存储下一条要执行指令的地址
  • 每周期自动 +4(字节寻址,32位指令)
  • 分支/跳转时加载新地址

2. 指令存储器 (Instruction Memory)

  • 根据 PC 地址读取指令
  • 只读,通常在取指阶段访问

3. 寄存器堆 (Register File)

  • 32个通用寄存器(x0-x31)
  • 2个读端口、1个写端口
  • x0 硬连线为0

4. 算术逻辑单元 (ALU)

  • 执行算术运算(加、减)
  • 执行逻辑运算(与、或、异或)
  • 执行比较运算(SLT)
  • 计算内存地址

5. 数据存储器 (Data Memory)

  • 存储程序数据
  • 读写操作在访存阶段进行

6. 控制单元 (Control Unit)

  • 解析指令 opcode
  • 生成控制信号

单周期数据通路

单周期设计中,每条指令在一个时钟周期内完成:

指令执行阶段:
┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│  取指    │ -> │  译码   │ -> │  执行   │ -> │  访存   │ -> │  写回   │
│ (Fetch) │    │ (Decode)│    │(Execute)│    │(Memory) │    │(WriteBack)│
└─────────┘    └─────────┘    └─────────┘    └─────────┘    └─────────┘
   PC+4          读寄存器       ALU运算        读写内存        写寄存器
   读指令        立即数扩展     分支判断

关键路径:取指 -> 寄存器读 -> ALU -> 内存访问 -> 寄存器写

缺点:时钟周期由最长指令(Load)决定,效率低下

多周期数据通路

将指令执行分为多个时钟周期,资源共享:

周期操作活跃组件
1取指PC, Inst Memory
2译码/读寄存器Register File
3执行/地址计算ALU
4访存/分支完成Data Memory
5写回Register File

优点

  • 功能部件复用(节省硬件)
  • 不同指令执行周期数不同
  • 更短的时钟周期

流水线 (Pipeline)

流水线通过重叠执行多条指令来提高吞吐量。

经典五级流水线

时间 ->
指令1: [IF][ID][EX][MEM][WB]
指令2:     [IF][ID][EX][MEM][WB]
指令3:         [IF][ID][EX][MEM][WB]
指令4:             [IF][ID][EX][MEM][WB]
阶段名称功能
IF取指 (Instruction Fetch)从内存读取指令,PC+4
ID译码 (Instruction Decode)解析指令,读取寄存器
EX执行 (Execute)ALU运算,计算地址
MEM访存 (Memory Access)读写数据内存
WB写回 (Write Back)结果写回寄存器

流水线冒险 (Hazards)

1. 结构冒险 (Structural Hazard)

  • 原因:硬件资源冲突
  • 示例:单端口内存同时需要取指和访存
  • 解决
    • 硬件:增加端口(哈佛结构)
    • 软件:插入气泡(Bubble)

2. 数据冒险 (Data Hazard)

  • 原因:指令间数据依赖
add x1, x2, x3   # I1
sub x4, x1, x5   # I2 需要 I1 的结果
and x6, x1, x7   # I3 需要 I1 的结果
or  x8, x1, x9   # I4 需要 I1 的结果

类型

  • RAW(写后读):I2 依赖 I1 的结果
  • WAR(读后写):通常不会在流水线中发生
  • WAW(写后写):不会在五级流水线中发生

解决方法

  • 转发/旁路 (Forwarding/Bypassing)

    EX阶段结果直接传递给下一条指令的EX阶段
    [IF][ID][EX]-->[MEM][WB]
         [IF][ID]-->[EX][MEM][WB]
                 ^^
                 转发路径
  • 加载-使用冒险 (Load-Use Hazard)

    lw  x1, 0(x2)    # I1
    add x3, x1, x4   # I2 需要 I1 的加载结果(无法转发)

    解决:插入1个气泡,或使用编译器调度

3. 控制冒险 (Control Hazard)

  • 原因:分支指令改变PC,但下一条指令已在流水线中
beq x1, x2, label  # 分支指令
add x3, x4, x5     # 无论是否分支,此指令已进入流水线

解决方法

  • 停顿 (Stall):等待分支结果(性能损失大)
  • 预测不跳转 (Predict Not Taken):假设分支不成立
  • 延迟分支 (Delayed Branch):编译器调度安全指令填充延迟槽
  • 分支预测 (Branch Prediction):硬件预测分支方向

分支预测 (Branch Prediction)

分支预测用于减少控制冒险带来的性能损失。

静态预测

策略描述准确率
总是不跳转假设分支总是不成立~50%
总是跳转假设分支总是成立~60%
向后跳转预测为真循环中常用(loop)~70%
编译器提示指令中附加预测提示可变

动态预测

1. 一位分支预测器

状态机:
     0 (Not Taken)
     ^      |
     |      v
     1 (Taken)

预测与实际结果一致:保持状态
预测与实际结果不一致:翻转状态

缺点:循环边界预测错误两次(进入和退出)

2. 两位分支预测器(饱和计数器)

状态机:
00: 强不跳转 (Strongly Not Taken)
01: 弱不跳转 (Weakly Not Taken)
10: 弱跳转   (Weakly Taken)
11: 强跳转   (Strongly Taken)

预测错误时移动一位,正确时继续强化

优点:对偶尔不成立的循环表现更好

3. 分支历史表 (BHT - Branch History Table)

PC低位索引 -> BHT -> 2位饱和计数器

BHT结构:
┌─────────────────┬──────────────────┐
│  分支指令PC低位  │  2位饱和计数器    │
├─────────────────┼──────────────────┤
│     ...         │      ...         │
└─────────────────┴──────────────────┘

4. 两级自适应预测器 (PAg/PAp/GAp)

  • 全局历史寄存器 (GHR):记录最近N次分支结果
  • 模式历史表 (PHT):根据历史模式选择预测器
GHR: 1010 (最近4次分支:跳、不跳、跳、不跳)

     v
PHT索引 -> 选择对应的2位计数器

分支目标缓冲 (BTB - Branch Target Buffer)

缓存分支指令的目标地址,避免计算延迟:

BTB结构:
┌──────────────┬──────────────┬──────────────┐
│ 分支指令PC    │ 目标地址      │ 预测信息      │
├──────────────┼──────────────┼──────────────┤
│  0x1000      │  0x2000      │  11 (强跳转)  │
│  0x1050      │  0x1800      │  01 (弱不跳)  │
└──────────────┴──────────────┴──────────────┘

Cache (高速缓存)

Cache 利用程序访问的局部性原理,在 CPU 和主存之间提供快速数据访问。

局部性原理

时间局部性:最近访问的数据很可能再次被访问

循环中的计数器变量

空间局部性:访问某个地址后,其邻近地址很可能被访问

顺序访问数组元素

Cache 基本结构

┌─────────────────────────────────────────┐
│              Cache 结构                  │
├─────────────┬─────────────┬─────────────┤
│    Index    │    Tag      │    Data     │
│  (索引位)   │  (标记位)   │  (数据块)   │
└─────────────┴─────────────┴─────────────┘

地址划分

32位物理地址:
┌─────────────────┬─────────────┬─────────────┐
│      Tag        │    Index    │   Offset    │
│    (标记位)      │   (索引位)   │  (块内偏移)  │
└─────────────────┴─────────────┴─────────────┘

直接映射 Cache (Direct Mapped)

每个内存块只能映射到唯一的 Cache 行:

Cache 行号 = (内存块地址) mod (Cache 行数)

示例:4行 Cache,块大小16字节
地址0x0000 -> Cache 行0
地址0x0010 -> Cache 行1
地址0x0020 -> Cache 行2
地址0x0030 -> Cache 行3
地址0x0040 -> Cache 行0 (冲突!)

优点:实现简单,查找快 缺点:容易产生冲突失效(Conflict Miss)

全相联 Cache (Fully Associative)

内存块可以映射到任意 Cache 行:

查找:比较所有行的 Tag

优点:无冲突失效 缺点:硬件复杂度高(需要并行比较器),不适合大容量

组相联 Cache (Set Associative)

Cache 分成若干组,每组包含多行:

2路组相联示例:
┌─────────────────────────────────────┐
│  组0  │  行0-0  │  行0-1  │         │
│  组1  │  行1-0  │  行1-1  │         │
│  组2  │  行2-0  │  行2-1  │         │
│  组3  │  行3-0  │  行3-1  │         │
└─────────────────────────────────────┘

组号 = (内存块地址) mod (组数)
组内任意行可存放该块

优点:平衡了冲突率和硬件复杂度

Cache 失效类型

类型原因解决策略
强制失效 (Compulsory)首次访问该块预取 (Prefetching)
容量失效 (Capacity)Cache 容量不足增大 Cache
冲突失效 (Conflict)映射冲突增加相联度

替换策略

策略描述特点
LRU (Least Recently Used)替换最久未使用的行实现复杂,命中率高
FIFO (First In First Out)替换最早进入的行实现简单
Random随机选择实现最简单
LFU (Least Frequently Used)替换访问次数最少的需要计数器

写策略

写命中 (Write Hit)

策略操作特点
写直达 (Write Through)同时写 Cache 和内存数据一致,速度慢
写回 (Write Back)只写 Cache,替换时写内存速度快,需要脏位

写不命中 (Write Miss)

策略操作
写分配 (Write Allocate)加载块到 Cache,再写入
非写分配 (No Write Allocate)直接写内存,不加载到 Cache

常见组合

  • 写直达 + 非写分配
  • 写回 + 写分配

多级 Cache

CPU -> L1 Cache -> L2 Cache -> L3 Cache -> 主存
      (32-64KB)   (256KB-1MB)  (4-64MB)
      1-3周期     8-15周期     20-50周期    200+周期
级别特点设计目标
L1分指令和数据 (Harvard)最小延迟
L2统一缓存平衡延迟和命中率
L3多核共享高命中率

包含策略

  • Inclusive:L1 内容一定在 L2 中
  • Exclusive:L1、L2 内容互不重复
  • Non-inclusive:介于两者之间

BUS (总线)

总线是连接 CPU、内存、I/O 设备的共享通信通路。

总线类型

总线功能示例
数据总线传输数据64位宽
地址总线传输地址32/64位宽
控制总线传输控制信号读写信号、中断等

总线仲裁

集中式仲裁

  • 链式查询:优先级固定,结构简单
  • 计数器定时查询:优先级可编程
  • 独立请求:每个设备独立请求,响应快

分布式仲裁

  • 设备自行竞争总线使用权

总线事务

典型读事务:
1. 主设备发送地址和读命令
2. 从设备准备数据
3. 从设备发送数据
4. 主设备接收数据

典型写事务:
1. 主设备发送地址和写命令
2. 主设备发送数据
3. 从设备接收并存储数据

计算机的性能指标

IC与Clock

指令数 (IC - Instruction Count):程序执行的总指令条数

时钟周期 (Clock Cycle):CPU 操作的最小时间单位

时钟频率 (Clock Rate)

f=1Tf = \frac{1}{T}

其中 TT 为时钟周期,ff 为时钟频率(单位:Hz)

CPI

CPI (Cycles Per Instruction):每条指令的平均时钟周期数

CPI=总时钟周期数指令数=i(CPIi×指令比例i)\text{CPI} = \frac{\text{总时钟周期数}}{\text{指令数}} = \sum_{i}(\text{CPI}_i \times \text{指令比例}_i)

不同指令的 CPI

指令类型典型 CPI说明
ALU 指令1单周期完成
Load2-5需要访存
Store1-2无需写回寄存器
分支1-3取决于预测成功率
乘法/除法3-32复杂运算

CPU Time

CPU 执行时间

CPU Time=指令数×CPI×时钟周期\text{CPU Time} = \text{指令数} \times \text{CPI} \times \text{时钟周期}

或:

CPU Time=指令数×CPI时钟频率\text{CPU Time} = \frac{\text{指令数} \times \text{CPI}}{\text{时钟频率}}

性能优化方向

方向方法影响
减少指令数更好的算法/编译器软件优化
降低 CPI流水线、超标量、更好的预测硬件架构
提高时钟频率更好的工艺、更短的流水线物理实现

Amdahl 定律

加速比=1(1f)+fs\text{加速比} = \frac{1}{(1 - f) + \frac{f}{s}}

其中:

  • ff:可优化部分在原执行时间中的比例
  • ss:该部分的加速倍数

Comments