CPU的分支预测
一、基础介绍
1.1 概念
分支预测(Branch Prediction)是现代CPU中用于优化指令流水线性能的关键技术,旨在解决条件分支指令(如 if-else
、循环等)导致的流水线停顿问题。
1.2 为什么需要分支预测
- 流水线技术:现代CPU通过流水线(Pipeline)并行处理指令,每个时钟周期完成一条指令的不同阶段(取指、解码、执行等)。
- 分支带来的问题:当遇到条件分支(如
if (x > 0)
)时,CPU需要等待条件结果(x是否大于0)才能决定下一条指令的位置。这会导致流水线停顿(Pipeline Stall),浪费多个时钟周期。 - 预测的必要性:为了不阻塞流水线,CPU会预测分支的方向(跳转或不跳转),并提前执行预测路径的指令。如果预测正确,性能无损;若错误,则需回滚(惩罚周期)。
1.3 分支预测的分类
1.3.1 静态分支预测
原理:无需历史信息,仅根据指令类型或编译器提示预测。
常见策略:
- 总预测不跳转:假设分支条件不满足,继续执行下一条指令。
- 反向跳转预测跳转:常用于循环(如
for
循环末尾的跳转指令)。 - 编译器提示:通过指令(如Intel的
likely/unlikely
宏)标记分支概率。
1.3.2 动态分支预测
原理:根据分支指令的历史行为动态调整预测策略。
关键结构:分支历史表(Branch History Table, BHT),记录每条分支指令的近期执行结果。
目前的 CPU 基本都采用的是动态分支预测,动态分支预测能够应对更多的执行情况,而且更容易产生正确的预测。
二、关键数据结构及作用
2.1 分支目标缓冲(Branch Target Buffer, BTB)
- 功能:存储分支指令的 地址(PC) 及其 目标地址(Target PC),实现快速跳转。
- 结构:
- 类Cache结构:通常为组相联(Set-Associative),通过分支指令的PC哈希到特定组。
- 条目内容:分支PC、目标PC、预测状态(如饱和计数器)、有效位等。
- 工作流程:
- 取指时查询BTB:若当前PC命中BTB,则预测跳转并直接获取目标PC。
- 执行后更新BTB:若实际分支方向或目标地址与预测不符,更新BTB条目。
2.2 返回地址栈(Return Stack Buffer, RSB)
- 功能:专门预测 函数返回(
ret
指令) 的目标地址,解决嵌套调用导致的预测难题。 - 结构:
- 硬件栈:通常是先进后出(LIFO)的栈结构,深度有限(如Intel Skylake的RSB深度为16)。
- 条目内容:函数调用(
call
)时压入的返回地址。
- 工作流程:
call
指令:执行时将下一条指令地址(返回地址)压入RSB。ret
指令:预测时从RSB弹出栈顶地址作为目标PC。- 栈溢出/欠载处理:若RSB空,退化为BTB预测;若溢出,丢弃最旧条目。
2.3 分支历史表(Branch History Table, BHT)
- 功能:记录分支指令的历史行为(跳转/不跳转),用于动态预测方向。
- 结构:
- 2位饱和计数器:每个分支指令对应一个状态机(如00-强不跳转,11-强跳转)。
- 索引方式:通过分支PC哈希或结合全局历史(GHR)索引。
- 更新规则:实际分支结果若跳转,则状态+1(上限11);否则-1(下限00)。
2.4 全局历史寄存器(Global History Register, GHR)
- 功能:记录近期所有分支指令的结果(跳转=1,不跳转=0),用于关联预测。
- 结构:
- 移位寄存器:长度固定(如10位),每次分支后左移并入最新结果。
- 与PC哈希:结合当前分支PC和GHR值索引BHT(如gshare算法)。
2.5 总结
数据结构 | 预测阶段作用 | 更新阶段操作 |
---|---|---|
BTB | 快速判断分支类型及目标地址 | 新增/更新分支条目及目标地址 |
BHT/PHT | 提供分支方向预测(局部/全局历史) | 调整饱和计数器状态 |
GHR | 记录全局分支历史,用于关联预测 | 左移并入最新分支结果 |
RSB | 专用于ret 指令的目标地址预测 |
call 压栈,ret 弹栈 |
三、预测流程
3.1 预测阶段的操作流程
当CPU需要预测一个分支指令时,按以下顺序操作四个表:
步骤1:取指阶段(Fetch Stage)
- 输入:当前指令地址(PC)。
- 操作:
- 查询BTB:
- 用当前PC的哈希值查询BTB,判断是否是已记录的分支指令。
- 命中BTB:获取分支类型(条件分支/函数调用/返回等)和目标地址。
- 未命中:默认为非分支指令,继续顺序取指。
- 分支类型判定:
- 条件分支(
jcc
):进入BHT/GHR预测流程。 - 函数调用(`call)**:记录返回地址到RSB。
- 函数返回(
ret
):从RSB弹出目标地址。
- 条件分支(
- 查询BTB:
步骤2:分支方向预测(条件分支专用)
- 输入:分支指令的PC、全局历史(GHR)。
- 操作:
- 索引BHT/PHT:
- 局部预测:仅用分支PC哈希索引BHT,获取2位饱和计数器状态。
- 关联预测(如gshare):将分支PC与GHR异或生成索引,查询PHT。
- 生成预测结果:
- 根据BHT/PHT中的状态(如11=强跳转)预测方向(跳转/不跳转)。
- 索引BHT/PHT:
步骤3:目标地址预测
- 条件分支:通过BTB获取目标地址(已提前存储)。
- 函数返回(`ret):从RSB栈顶弹出目标地址。
- 函数调用(
call
):目标地址直接由指令给出,同时将下一条指令地址(返回地址)压入RSB。
步骤4:预取指令
- 根据预测方向和目标地址,从预测路径(跳转目标或顺序下一条)预取指令到流水线。
3.2 执行阶段验证与更新
当分支指令实际执行后,验证预测是否正确,并更新相关数据结构:
步骤1:验证结果
- 实际跳转方向:比较预测方向与实际结果(跳转/不跳转)。
- 实际目标地址:比较预测目标地址与实际地址(如间接跳转地址变化)。
步骤2:更新数据结构
- BHT/PHT更新:
- 若预测错误,根据实际结果调整饱和计数器(例如实际跳转则+1,否则-1)。
- 在关联预测中,更新GHR(将实际结果左移并入全局历史)。
- BTB更新:
- 若分支指令未记录在BTB中,新增条目(PC→目标地址)。
- 若目标地址变化(如间接跳转),更新BTB中的目标地址。
- RSB维护:
call
执行时:将返回地址压入RSB。ret
执行时:若RSB未空,弹出栈顶地址;若栈空则通过BTB或顺序地址回退。
- GHR更新:
- 将实际分支结果(1=跳转,0=不跳转)左移并入GHR寄存器。
- 例如:GHR原为
0b1011
,实际跳转→更新为0b10111
(保留固定长度)。
四、总结
分支预测通过 BTB(目标地址)、BHT/PHT(方向预测)、GHR(全局历史)、RSB(函数返回) 四个表的协同操作,在取指阶段快速预测分支行为,最大化流水线吞吐量。预测错误时,通过更新机制动态修正表的记录,逐步提升预测准确率。理解这一流程有助于在代码中规避分支密集或模式随机的逻辑,从而减少预测错误带来的性能损失。
CPU的分支预测
http://candyb0x.github.io/2025/03/17/CPU的分支预测/