想象一场高速赛车比赛:赛车手以每小时 300 公里的速度冲向连续弯道,坐在副驾的领航员必须在过弯前零点几秒喊出方向——“左弯!全开!”如果喊对了,赛车不减速丝滑过弯;如果喊错了,车手必须急刹车、重新调整方向,损失宝贵的零点几秒。CPU 处理器内部每秒钟都在经历数亿次这样的“过弯”:流水线里指令明明在顺序执行,突然遇到一个岔路口——分支指令(比如 if/else、循环、函数调用),到底走哪条路?等到执行阶段算完再决定,流水线就得空等好几拍,像赛车踩死刹车。于是现代处理器宁可提前“赌一把”,也不愿停下来。赌法从新手领航员的固定口诀(静态预测),到老手背下每个弯道的小本本(BTB 缓冲器),再到记住整段赛道节奏的超级领航员(全局历史预测器),一路把“过弯”准确率从六成提到 99% 以上。这篇文章,我们就钻进这台“赛车”的引擎舱,看看领航员们是如何工作的。

岗位关联

分支预测是现代 CPU 微架构的核心性能引擎之一,理解它才能读懂 IPC、流水线调优和性能分析工具的底层逻辑——这正是 AI 从业者做高性能推理系统、算子性能优化时无法绕开的硬件底座。

目录
  1. 可视化演示
  2. 流水线基础
  3. 静态预测
  4. 动态预测
  5. BTB缓冲器
  6. 高级预测器
  7. 预测准确率
  8. 超标量挑战
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示分支目标缓冲器BTB查表预测、两位饱和计数器状态机与误预测流水线冲刷恢复,切换模式掌握分支预测核心机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

分支目标缓冲器BTB工作原理 BTB缓存分支指令地址与目标地址映射,取指阶段查表预取目标地址减少控制停顿 命中时直接跳转到缓存目标,未命中则顺序执行并回填BTB表项供后续预测使用 BTB[PC] = target_addr, hit -> prefetch target, miss -> sequential + backfill BTB配合方向预测器构成完整分支预测单元

流水线基础

赛车疾驰在笔直的大直道上,一箱一箱的汽油(指令)正通过五站接力传递到引擎:加油、点火、做功、排气、记录数据。CPU 里的五级流水线也是同样的接力赛——取指(IF)、译码(ID)、执行(EX)、访存(MEM)、写回(WB)五个站点各司其职,每个时钟周期都有一站同时开工,让指令像流水线一样吞吐。一切看起来完美,直到前方出现一个分岔路口:一条分支指令。

问题来了:分支指令要等执行站算完比较结果才知道往哪走,可取指站这个“加油工”已经等在下一条指令面前,不知道该准备哪桶油。如果所有人都停下来等结果,流水线就像赛车踩死刹车,连续好几个周期空转,一个“泡泡”接着一个“泡泡”。这种因为“下一步去哪”引发的停顿,在计算机体系结构里叫作控制冒险(Control Hazard),它是流水线设计里最棘手的敌人之一。

早期的处理方式很笨:遇到分支就刹车。裁判先让流水线停两到三拍,等执行站算完方向再继续。这种“冲洗+停顿”的代价随流水线深度放大——现代处理器流水线动辄十几级,每遇到一次分支就空转十几拍,性能根本无法接受。于是人们想到:与其等,不如猜。这就是分支预测的诞生前提:预测对了零代价,预测错了才付一次冲刷的学费。

靠着这条“成本公式”,我们就能理解为什么赛车手宁可让领航员大胆喊方向,也不愿每个弯道都踩刹车——停下来一定亏,猜对了就是血赚。

CPI实际=CPI理想+f分支×p误预测×n惩罚\displaystyle CPI_{实际} = CPI_{理想} + f_{分支} \times p_{误预测} \times n_{惩罚}
这条公式是说:实际每条指令的平均耗时,等于理想情况加上“分支占比 × 猜错比例 × 猜错一次的代价”。
  1. CPI 实际每条指令平均消耗的时钟周期数,是衡量处理器执行效率的核心指标。
    CPI实际CPI_{实际}
  2. CPI 理想流水线满负荷、零冒险时的理论最低周期数。
    CPI理想CPI_{理想}
  3. 分支频率 f动态执行的指令里分支指令占的比例,典型程序在 15%~25%。
    f分支f_{分支}
  4. 误预测率 p分支预测器猜错方向的概率。
    p误预测p_{误预测}
  5. 惩罚周期 n猜错一次要丢弃多少条已经进入流水线的错误指令。
    n惩罚n_{惩罚}

静态预测

新手领航员没什么实战经验,手里只有一本印好的赛道手册,上面是几条固定的指挥口诀:“大多数弯道不拐”“循环的回头弯多半往回走”“工程师在手册上标了重点的弯道要拐”。这些规则在比赛开始前就定死了,不会根据实际情况改变——这就是静态预测(Static Prediction)的本质:用固定规则猜,不学习、不更新。

三种常见静态策略各有各的适用场景。第一是“默认不跳转”:大多数 if 条件的 false 分支更容易被执行,那就永远猜不跳。第二是“向后跳转”:循环结构在汇编层面表现为“若条件成立则跳回前面的循环头”,方向通常是向后的,所以预测“往后跳”对循环很友好。第三是“编译器提示”:编译器通过静态分析,或者利用程序员写的 __builtin_expect / likely / unlikely 宏,在生成机器码时把更可能走的分支配成顺序执行,降低跳转概率。

静态预测简单廉价、零硬件开销,但天花板也很明显:程序的行为是动态变化的,今天这个弯道总是左转,明天换了输入数据就成了右转,固定口诀来不及调整,准确率只能停在及格线附近。

不过,固定口诀解决不了“程序员写了个随输入变化的 if”,真正的破局之道,是让领航员在赛道上边跑边学。

准确率默认不跳=N不跳转N所有分支≈60%∼70%\displaystyle 准确率_{默认不跳} = \frac{N_{不跳转}}{N_{所有分支}} \approx 60\% \sim 70\%
它说明“永远猜不跳转”在典型整数程序里大约能蒙对六到七成——听起来还行,但离高性能计算的需求差得远。
  1. 不跳转次数程序实际运行中,分支指令判定为“不跳转”的动态执行次数。
    N不跳转N_{不跳转}
  2. 分支总次数程序运行期遇到的所有分支指令的动态总和。
    N所有分支N_{所有分支}
  3. 六到七成典型整数程序里,循环出口、函数返回、if 假分支往往落在不跳转路径,所以“默认不跳”能猜中一半多。
    60%∼70%60\% \sim 70\%

动态预测

老领航员开始随身带一个记事本,每过完一个弯道就记下实际方向:“3 号弯,上次是左拐,这次大概率还往左。”——这就是动态预测(Dynamic Prediction)的雏形:用硬件记录每个分支最近一次的结果,作为下一次预测的依据。最简单的 1 位预测器只有 1 个比特,1 代表上次跳转、0 代表上次没跳,预测规则就是“上次怎样,这次还怎样”。

但 1 位预测器有个著名的“单比特问题”:假设一个 for 循环要执行 10 次,前 9 次都跳回循环头,第 10 次条件不成立退出循环。1 位预测器在第 10 次会猜“跳转”,错一次;下一次进入同一个循环,它记得上次是“不跳”,于是第一轮又猜“不跳”,再错一次——一个循环错两次,仿佛领航员总在最后关头栽跟头。解决思路是给它增加一点“怀疑缓冲”:两位饱和计数器(2-bit Saturating Counter)用 2 个比特表示 4 个状态——强不跳、弱不跳、弱跳、强跳,只有当实际方向连续两次与当前偏向相反时,才翻转预测。就像领航员改口前要确认两次,不会被一次反常带偏。

两位计数器是现代分支预测的最小积木,后面要讲的所有高级预测器,最终都靠一堆这样的计数器来投票。它的状态转移规则如下:

领航员的小本本让他从“总在蒙”进化到“会改口”,可新的问题马上出现:本子记在哪、怎么快速翻到对应弯道、以及——光知道要拐,还不知道拐去哪条岔路。

st+1={min⁡(st+1,3)实际跳转时max⁡(st−1,0)实际不跳时\displaystyle s_{t+1} = \begin{cases} \min(s_t + 1, 3) & \text{实际跳转时} \\ \max(s_t - 1, 0) & \text{实际不跳时} \end{cases}
向上的“加热区”和向下的“冷却区”各有两个档位:只有连续两次撞上相反结果,才会从“强”翻到“弱”再翻向另一边。
  1. 状态变量 s当前计数器取值,0~3 分别对应强不跳、弱不跳、弱跳、强跳。
    sts_t
  2. 实际跳转加一方向偏跳转时状态“加热”一格,但封顶为 3,防止过度自信。
    min⁡(st+1,3)\min(s_t + 1, 3)
  3. 实际不跳减一方向偏不跳转时状态“冷却”一格,但最低为 0。
    max⁡(st−1,0)\max(s_t - 1, 0)
  4. 预测规则状态值 ≥2 时预测跳转,否则预测不跳——以 2 为分水岭。
    预测=(s≥2) 时跳转预测 = (s \ge 2) \text{ 时跳转}

BTB缓冲器

领航员光靠记事本记住了“3 号弯该往左”,可赛车手接着要问一句:“往左是哪条路?通往哪个出口?”——对应到 CPU 里就是:预测“要跳转”之后,下一拍该从哪个地址取指令?如果每次都要等执行站算出跳转目标,提前预测就白做了。于是 BTB(Branch Target Buffer,分支目标缓冲器)闪亮登场:它不只是记事本,更是一张“弯道地址册”。

BTB 本质上是一个小而快的缓存,放在取指阶段旁边,以分支指令的 PC 地址作为“钥匙”(索引和标签),每个条目存储四样东西:有效位、标签、目标地址、预测状态位(通常内嵌两位计数器)。取指阶段在取到一条指令的同时,会用它的 PC 去查 BTB:如果命中且状态位预测跳转,下一拍直接跳去目标地址取指,零延迟丝滑过弯;如果未命中,说明这是个没见过的弯道,只能老实地在译码站等执行结果算出来,白白损失 2~3 拍。

所以 BTB 的命中率直接影响流水线的流畅度。它和两位计数器是黄金搭档:一个负责“要不要拐”,一个负责“拐到哪去”。把公式 1 展开,加入 BTB 未命中的惩罚,就得到带 BTB 的实际 CPI 模型:

有了 BTB 这本“地址册”,领航员已然是老手。可真正的顶级领航员还嫌不够:他们不记单个弯道,而是背下整段赛道节奏——下一个弯道往往和前面几个弯道连环相关。

CPIBTB=1+f分支×(1−hBTB)×2\displaystyle CPI_{BTB} = 1 + f_{分支} \times (1 - h_{BTB}) \times 2
这里假设遇到一个 BTB 里没有记录的分支,要空等 2 拍才知道目标地址;只要 BTB 命中率高,这部分损失就趋近于零。
  1. 基准周期 1理想情况下每条指令占 1 个周期。
    11
  2. 分支占比 f分支指令在动态指令流中的比例。
    f分支f_{分支}
  3. BTB 未命中率查 BTB 没找到该 PC 对应条目的概率。
    1−hBTB1 - h_{BTB}
  4. 两拍代价目标地址晚两个周期才算出,流水线被迫插入两个气泡。
    ×2\times 2

高级预测器

顶级领航员的脑子里装的不是“3 号弯向左”,而是一整段赛道记忆:“上一个 S 弯是左、右、左,这个发卡弯十有八九是往右。”CPU 里对应的就是基于历史模式的预测器。局部历史预测器(Local History Predictor)给每条分支单独维护最近 2~3 次的结果模式(比如 TT、TN),用“PC + 局部历史”去索引一张模式历史表(PHT),表里每个位置放一个两位计数器。它的洞察是:同一个分支的近期行为常常有内在规律。

全局历史预测器(Global History Predictor)走得更远:把所有分支的最近结果统一压进一个全局历史寄存器(GHR),每次新结果左移入栈,再拿它和当前分支 PC 的低位做异或,用异或结果去查计数器表。它能捕捉“不同分支之间”的相关性——比如 A 分支跳转后 B 分支大概率跳转,这是单看一条分支学不到的。把“历史收集”和“计数器决策”拆成两级,就是经典的两级自适应预测器架构(如 gshare)。

不过异或索引也有副作用:两个不同 PC 的分支可能映射到同一个计数器,互相干扰导致准确率下降,这个现象叫分支混淆(Branch Aliasing)。设计者通常用更长的哈希、更多的索引位来稀释冲突。

记住赛道节奏后,预测准确率能突破 95%。但领航员再强也得算账:准确率这枚硬币的反面,是猜错时付出的真金白银——我们来看性能账本。

索引=PC低位⊕GHR\displaystyle 索引 = PC_{低位} \oplus GHR
一句话:用“这是哪条弯道”和“刚跑过的弯道节奏”混合出一个查表位置,让预测器既认路又认节奏。
  1. PC 低位分支指令地址的低 8~12 位,用来在表里区分不同分支。
    PC低位PC_{低位}
  2. 全局历史寄存器一个移位寄存器,记录最近 k 次分支的实际方向,1 表示跳转,0 表示不跳。
    GHRGHR
  3. 异或运算把两个比特串按位异或,起到混淆打散的作用,减少索引冲突。
    ⊕\oplus
  4. 查表决策用索引结果到模式历史表中取一个两位计数器,按阈值判断出最终预测。
    PHT[index]PHT[index]

预测准确率

准确率就是领航员的胜率,它直接写进赛车手的成绩单。回到第一节公式 1:误预测损失 = 分支占比 × 误预测率 × 惩罚周期。现代高性能 CPU 流水线深达 14~20 级,猜错一次,意味着有 14~20 条指令已经按照错误路径冲进了流水线,全部要作废重来。这就像赛车以 300 码冲进错误赛道后,要急刹、倒回、重新加速——每一秒都是滚烫的钞票。

实测数据能直观说明预测器的进化:完全不预测,每个分支停 3~5 拍,CPI 轻松超过 2;用静态预测,准确率约 65%;两位计数器出场,升到 85% 左右;全局历史/两级自适应预测器,92%~96%;现代旗舰 CPU 使用的 TAGE 类预测器,可达 98%~99.5%。从 85% 到 99%,表面只差 14 个百分点,在深流水线里折算成性能却是天壤之别。

不信?代入一组典型数字算给你看:分支占比 20%,预测准确率 95%,流水线深 15 级,那么误预测给每条指令平均增加的周期数是 0.2 × 0.05 × 15 = 0.15。若理想 CPI 是 0.5,这意味着性能直接损失三成——而这还只是“平均值”,最坏路径上 miss 密集,性能雪崩更吓人。

账算明白了:准确率每提升一个小数点,都是真金白银的吞吐量。可当赛道从双人赛车升级成 F1 车队车库——多辆赛车并排冲刺,领航员们能同时喊对吗?

损失CPI=f分支×p误预测×n惩罚=0.2×0.05×15=0.15\displaystyle 损失_{CPI} = f_{分支} \times p_{误预测} \times n_{惩罚} = 0.2 \times 0.05 \times 15 = 0.15
决定这条账的,不是预测器有多聪明,而是“猜错后要扔多远”——流水线越深,误预测越贵。
  1. 分支频率 0.2动态指令中五分之一是分支。
    f分支=0.2f_{分支}=0.2
  2. 误预测率 0.0595% 准确率意味着每 100 次猜错 5 次。
    p误预测=0.05p_{误预测}=0.05
  3. 惩罚 15 拍15 级流水线意味着错误指令已经冲出去 15 条之远。
    n惩罚=15n_{惩罚}=15
  4. 总损失 0.15 CPI平均每条指令多付 0.15 拍,相当于 CPI 从 0.5 涨到 0.65。
    损失CPI=0.15损失_{CPI}=0.15

超标量挑战

现代 CPU 早已不是单辆赛车,而是八缸发动机同时工作:每个时钟周期要取 4~8 条指令,相当于 4~8 辆赛车并排驶向连续弯道,每一辆都需要领航员提前喊话。坏消息是,一个周期里可能同时出现好几条分支指令。按宽度 4、分支占比 20% 计算,约 59% 的周期至少遇到一条分支;宽度 8 时,这个概率超过 83%。预测器必须提供足够的“多路端口”同时输出多个预测,复杂度陡增。

第二个挑战是延迟。预测器位于取指关键路径上,所有判断必须在一个周期内完成,否则取指站反而要等预测结果,得不偿失。可 TAGE 这类高准确率预测器动辄要查好几级历史表,延迟和容量像鱼和熊掌。第三个挑战是面积与功耗:动辄数百 KB 的历史表、成千上万个计数器,在手机 CPU 的功耗预算里是高不可攀的奢侈品,设计者必须在准度、延迟、面积、功耗之间反复拔河。

还有一个老大难:间接跳转(Indirect Branch)。switch 语句的跳转表、C++ 虚函数调用、函数指针,目标地址散落在内存各处,BTB 条目被无限大的地址空间稀释,预测准确率明显低于普通条件分支。这类分支几乎成了现代预测器的“鬼门关”,需要另配专用的小型间接跳转预测器来救火。

从五级流水线的“踩刹车”到超标量的“多路同时预测”,分支预测始终是 CPU 设计中最考验平衡功夫的领域。领航员要快、要准、还要省油——这门手艺,直到今天仍在不断进化。

P至少一条分支=1−(1−f分支)W\displaystyle P_{至少一条分支} = 1 - (1 - f_{分支})^W
它回答的问题是:每周期取 W 条指令,其中至少夹带一条分支的概率有多大——宽度越大,预测器的压力越大。
  1. 取指宽度 W处理器每周期取多少条指令,现代高性能 CPU 通常为 4~8。
    WW
  2. 单条不是分支一条指令不是分支指令的概率。
    1−f分支1 - f_{分支}
  3. W 条全不是分支一整组指令里完全没有分支的概率。
    (1−f分支)W(1 - f_{分支})^W
  4. 补集得到至少一条用 1 减去上式,即得到“至少有一条分支”的周期占比。
    1−(1−0.2)4≈0.591 - (1 - 0.2)^4 \approx 0.59

AI 应用场景

CPU 微架构设计验证

阶段:处理器设计阶段的架构探索与验证

解决的问题:在芯片流片前,架构师需要决定 BTB 容量、历史寄存器长度、计数器表大小等参数。传统做法靠经验和少量手算推估,容易高估或低估某个配置,导致流片后性能不达标。借助周期精确模拟器,可以在设计早期系统扫描配置空间,用数据代替拍脑袋。

具体案例:架构师在 gem5 模拟器中搭建一个 8 发射超标量模型,分别配置“4K 熵 BTB + 4K 计数器”和“16K 熵 BTB + 16K 计数器”两组方案,跑 SPEC CPU 2017 基准测试,对比两个配置下的准确率、IPC、动态功耗,最终选定在面积约束下性价比最高的方案。

编译器代码布局优化

阶段:代码生成与编译器后端优化

解决的问题:程序员写的 if/else 顺序,直接影响分支路径的局部性。传统方式下编译器只按源码顺序生成指令,热路径可能被拆散到跳转的两端,导致预测器 miss 率上升。利用分支预测原理,编译器可以重新排列基本块,把大概率路径排成顺序执行。

具体案例:编译器工程师在 LLVM 中启用 PGO(Profile-Guided Optimization),先用插桩运行收集每个分支的真实偏转频率,再用 __builtin_expect 标记热路径,调整基本块布局,使某数据库查询算子的 branch-misses 率下降 12%,整体查询延迟降低 8%。

线上服务性能剖析

阶段:系统性能分析与调优

解决的问题:生产环境中微服务响应延迟抖动,常见原因之一就是分支误预测在热路径上集中爆发。传统“盲调代码”效率低,借助硬件性能计数器可以精确定位高 miss 的函数,配合改写手段(查表替换分支、排序数据让分支可预测)显著降延迟。

具体案例:SRE 团队用 perf stat -e branch-instructions,branch-misses 定位到某鉴权模块的二分查找函数,分支误预测率高达 18%。把无序的用户表按 ID 排序后遍历查找,分支变得规律可预测,误预测率降到 4%,接口 P99 延迟从 210ms 降到 165ms。

Python 演示

python48 行
class TwoBitPredictor:    """两位饱和计数器: 0=强不跳 1=弱不跳 2=弱跳 3=强跳"""     def __init__(self, init=2):        self.state = init     def predict(self):        return self.state >= 2     def update(self, taken):        if taken:            self.state = min(self.state + 1, 3)        else:            self.state = max(self.state - 1, 0)  def run(branches, predictor):    hit = 0    for taken in branches:        if predictor.predict() == taken:            hit += 1        predictor.update(taken)    return hit / len(branches)  def make_loop(times, body=3):    """模拟 for 循环: body 次循环体跳转 + 1 次出口不跳, 重复 times 轮"""    seq = []    for _ in range(times):        seq += [True] * body + [False]    return seq  def make_random_pattern():    """构造一个交替抖动、无明显规律的分支序列"""    return [True, False, True, False, False, True, False, True,            True, False, True, False, True, False, False, True,            False, True, False, True]  loop_seq = make_loop(20)random_seq = make_random_pattern() p1 = TwoBitPredictor()p2 = TwoBitPredictor() print("循环场景准确率:", round(run(loop_seq, p1), 4))print("交替场景准确率:", round(run(random_seq, p2), 4))
目的:用 Python 实现一个两位饱和计数器分支预测器,对比“规律循环”与“随机抖动”两类分支序列的预测准确率,直观体验为什么预测器需要“历史模式”。
  1. def predict(self): return self.state >= 2状态大于等于 2 就预测“跳转”,这是两位预测器的决策阈值。
  2. self.state = min(self.state + 1, 3)实际确实跳转时状态“加热”一格,上限封顶为 3,防止过拟合。
  3. self.state = max(self.state - 1, 0)实际没跳时状态“冷却”一格,下限保持 0。
  4. seq += [True] * body + [False]构造典型的循环分支流:前 3 次回跳、第 4 次退出,周而复始。
  5. run(branches, predictor)依次喂入每条分支的真实方向,统计预测命中率。

常见误区

误区分支预测要是猜错了,程序算出来的结果不就错了吗?那程序员写代码时不是得时刻担心自己的 if 会不会被 CPU 搞乱?

正解完全不用担心。硬件会在执行阶段发现预测错误,立即把已经进入流水线的错误指令全部作废(flush),从正确路径重新取指,并恢复寄存器现场——这个过程对软件完全透明。猜错的代价只是白等十几个周期,影响的是速度,绝不是正确性。就像领航员喊错方向,赛车手急刹车再掉头,多耗几秒,但最终一定会开到正确目的地。

为何常见:很多人把“预测”联想成“推断结论”,以为预测器会改变执行语义,其实它只是性能加速装置,和“结果算不算得对”毫无关系。

误区CPU 每次遇到 if 都要做预测,程序里的 if 越少越流畅,最好全部改写成无分支的位运算或查表。

正解分支的数量不是关键,分支的规律性才是。循环里每次条件都成立、方向高度规律的分支,两位计数器学一次就能连续命中几乎 100%,成本约等于零;真正贵的是数据依赖型分支——比如二分查找最后一步目标值落在哪边,完全随机,预测器再怎么学也猜不准。这类分支才值得用查表、算术表达式等无分支手段换掉。盲目消灭 if,常常把好读的代码改复杂,收益却微乎其微。

为何常见:把“每个分支都要花 15 拍”的错误前提合理化了,忽略了可预测分支与难预测分支之间的天壤之别。

误区BTB 是缓存,缓存都是越大命中率越高,那把 BTB 做成 L2 那么大不就好了吗?

正解BTB 位于取指关键路径,查询必须在不到 1 个周期内完成。容量增大,SRAM 的访问延迟和面积功耗同步上涨,超过一定规模后反而拖慢每一拍取指,收益被延迟成本抵消。现代设计通常用几十 KB 的分级 BTB(第一级小而快、第二级大而全)来平衡,而不是一味求大。

为何常见:直觉上“缓存都越大越好”,但 BTB 的第一优先级是“快”而不是“大”,这是一类“用缓存思维理解关键路径部件”的典型误区。

误区宣传材料都说现在预测准确率超过 99%,分支预测早就不是问题了,性能调优根本不用管它。

正解99% 是跨程序的平均值,个别路径依然是重灾区:switch 跳转表、虚函数调用这类间接跳转,目标地址太散,BTB 命中率不高;数据相关的分支(哈希查找、二分循环中依赖数据本身的分支)无论多聪明的预测器都难猜。剩余的那一点点 miss 往往集中在少数热分支上,而这恰恰是性能优化真正的战场。平均概率掩盖了长尾热点。

为何常见:拿平均值安慰自己,忽视了“性能瓶颈永远在 miss 密度最高的那条路径”这一基本原则。

面试题

Q1. 两位饱和计数器一共有多少个状态?

单选题 · easy

  • A.2 个
  • B.3 个
  • C.4 个← 正确答案
  • D.8 个

答案:C。两位比特可以编码 2²=4 种状态:强不跳、弱不跳、弱跳、强跳。它比 1 位计数器多了一档“弱”缓冲,必须连续两次相反结果才翻转,像领航员改口前要确认两遍。

Q2. 分支预测正确时,流水线需要做什么处理?

单选题 · easy

  • A.冲刷已取得的指令
  • B.插入三个气泡
  • C.什么都不用做,继续流动← 正确答案
  • D.清空 BTB 缓冲器

答案:C。预测正确意味着取指阶段已经沿真实路径前进,流水线不需要任何额外操作,自然推进即可。只有预测错误才需要冲刷错误路径的指令并重新取指,就像领航员喊对方向时赛车手只管全开油门。

Q3. 静态预测策略“默认不跳转”在典型整数程序上的准确率大约是多少?

单选题 · easy

  • A.20%~30%
  • B.40%~50%
  • C.60%~70%← 正确答案
  • D.90%~99%

答案:C。典型程序中约六到七成动态分支落点在不跳转路径(循环出口、函数返回等场景),所以“永远猜不跳”大约能蒙对六到七成。但这离高性能需求还差得远,所以需要引入动态预测。

Q4. 某处理器误预测惩罚为 14 个周期,分支频率 20%,误预测率 5%,误预测造成的平均 CPI 损失是多少?

单选题 · mid

  • A.0.07
  • B.0.14← 正确答案
  • C.1.4
  • D.0.7

答案:B。损失 = 分支频率 × 误预测率 × 惩罚周期 = 0.2 × 0.05 × 14 = 0.14 CPI。若理想 CPI 为 0.5,性能约下降三成,足见深流水线时代误预测成本之高昂。

Q5. BTB(分支目标缓冲器)中每个条目的“标签”主要用来做什么?

单选题 · mid

  • A.存储跳转后要执行的指令数据
  • B.快速比对分支指令的 PC 是否匹配该条目← 正确答案
  • C.保存两位饱和计数器的历史模式
  • D.指示该条目是否被分支写回覆盖

答案:B。BTB 以分支指令的 PC 为“钥匙”,标签就是 PC 的一部分,在取指阶段做旁路快速比对。命中说明这条分支之前见过且记住了目标地址,下一拍就能直接跳过去取指令。

Q6. 全局历史预测器中的 GHR(全局历史寄存器)存储的是什么?

单选题 · mid

  • A.最近 k 次分支的实际方向序列← 正确答案
  • B.所有 BTB 条目的目标地址
  • C.当前分支的 PC 低位
  • D.数据缓存中的跳转表内容

答案:A。GHR 是一个移位寄存器,每条分支执行完就把实际方向(1 跳转、0 不跳)左移入栈,记录最近 k 次结果,用来捕捉跨分支的节奏相关性。PC 低位只是参与索引哈希,不是 GHR 的存储内容。

Q7. 动态分支预测器通常需要哪些输入信息?

多选题 · mid

  • A.分支指令的 PC← 正确答案
  • B.最近执行分支的历史模式← 正确答案
  • C.分支可能的目标地址← 正确答案
  • D.数据缓存中的内存数值

答案:ABC。PC 用来区分不同分支,历史模式用来学习规律,目标地址由 BTB 提供用于取指。数据缓存里的数值一般不参与分支方向判断,除非是依赖数据的间接跳转,但这不是常规预测器的输入。

Q8. 两位饱和计数器对“交替无规律分支序列”(如 T,F,T,F,T,F……)的准确率大约是多少?

单选题 · hard

  • A.接近 100%
  • B.约 75%
  • C.约 50%← 正确答案
  • D.约 25%

答案:C。两位计数器需要连续两次相反结果才翻转,面对严格交替的序列,每次预测都等于上一拍结果,恰好与实际相反,准确率约 50%,和抛硬币一样。这说明无规律分支需要依靠历史模式的高级预测器来救场。

Q9. 以下哪些措施可以降低分支误预测带来的性能损失?

多选题 · hard

  • A.用查表或算术表达式替代难预测的分支← 正确答案
  • B.提高 BTB 容量以减少目标缺失← 正确答案
  • C.将数据排序,把条件分支变成规律分支← 正确答案
  • D.增加流水线深度以掩盖错误

答案:ABC。查表替代随机分支、提高 BTB 命中率、排序数据让分支有规律,都能直接降 miss。增加流水线深度反而放大误预测惩罚——更深意味着错误指令冲得更远、要丢得更多,方向完全反了。

Q10. 超标量处理器中预测器设计面临的主要挑战有哪些?

多选题 · extreme

  • A.每周期可能遇到多条分支,需要多端口预测← 正确答案
  • B.预测延迟必须被压缩在一个周期内← 正确答案
  • C.面积与功耗预算受限← 正确答案
  • D.必须做到 100% 精确才可用

答案:ABC。取指宽度越大,单周期遇到多条分支的概率越高,需要多端口同时输出预测;复杂预测器延迟与容量又相互制约,面积功耗同样受限。但 100% 精确既不可能也没必要,设计目标只是把 miss 率压到足够低、把热路径的代价降到可控。