H21

分支预测与推测执行

点击正文段落选定收藏或划线位置;未选定时使用屏幕内第一段。

课内目录

1 分支为什么打断流水线

在 1 台装有 16 核 CPU、64 GB DDR5 内存、PCIe 4.0 NVMe 固态盘和 25 GbE 网卡的服务器上,数据进入内存之后,处理速度仍可能差很多。程序扫描同样大小的记录数组,执行同样的条件判断,换成另批输入便明显变慢。此时磁盘带宽可能有余量,网卡也没有跑满。原因可能藏在 CPU 对“接下来执行哪里”的判断里。这与机箱是塔式、机架式还是小型工作站没有直接对应关系,关键是实际工作负载和处理器执行行为。

流水线让不同指令的处理阶段相互重叠:前面的指令正在计算,后面的指令已经译码,更后面的指令正在取入。条件分支会根据比较结果选择后续地址。例如扫描传感器记录时,if (temperature > limit) 决定是否更新报警统计。只有温度和阈值都就绪,比较才有结果;可取指部件希望更早知道该去哪个地址。若始终等到条件确定才继续,后续阶段便可能缺少工作,形成通常所说的气泡。

气泡是有效指令供应不足的直觉表达,不表示芯片里真有空白物体,也不表示所有执行部件同时停下。现代 CPU 可能继续处理其他已就绪的指令,但正确路径没有及时进入,仍会损失潜在吞吐。若分支依赖的数据刚好发生缓存未命中,条件确定还会推迟。这里同时存在访存等待和控制流不确定,不能看到慢就把全部周期归因于分支。内存容量足够也不等于每次读取都及时命中缓存。

分支产生的取指不确定与核对过程
分支产生的取指不确定与核对过程

分支预测的任务,就是在真实条件尚未确定时,提供可能的后续取指地址。预测正确时,流水线提前做的工作有机会成为有效结果;预测错误时,处理器需要转向正确路径。预测器没有改变程序的判断标准,也不会因为猜测就让错误报警永久写入统计。它是在争取时间,同时接受猜错后要回收工作的成本。

本课把“方向”与“目标”分开:方向回答跳不跳,目标回答从哪个地址继续。简单条件跳转的候选地址通常比较明确,间接调用则可能在多个目标之间选择。入门时先抓住这层区别,不需要记住某款芯片的流水线级数。标称频率也不能替代分析:同为 3.0 GHz,若每条有效指令平均需要更多周期,完成同批记录的时间仍会增长。

2 静态与动态预测直觉

静态预测使用不随近期执行结果持续变化的规则。概念上,可以始终猜“不跳转”,也可以根据跳转方向采用固定倾向。例如循环末尾向前面的代码跳转,常在多次迭代中成立,退出时才不成立。这样的规则能解释为什么某些控制流天然容易猜,但不能把“向后跳转总被预测为跳转”当作所有真实 CPU 的统一规格。编译布局、指令形式和具体实现都会影响行为。

动态预测会参考执行历史。设某个分支在扫描过程中长期不触发异常,那么依据过去结果继续猜“不触发”通常合理。如果输入阶段改变,异常开始持续发生,预测状态也需要适应。历史不是保证,它提供的是对未来的线索。记录内容表面杂乱,也可能存在稳定类别、周期或相邻关联;反过来,每次运行得到相同结果序列,也不代表硬件能完整记住任意长序列。

可以用 2 位饱和计数器建立直觉:它有强烈不跳、倾向不跳、倾向跳转、强烈跳转这 4 个状态。每次实际跳转就向跳转方向移动,每次不跳就向另一方向移动,到边界后不再增加。相邻的 2 个状态给出相同预测,因此偶发反例不一定立即翻转预测方向。这里的 2 位模型是教学工具,真实处理器可能结合更多历史信息;本课不展开预测表结构和复杂算法。

对循环而言,连续 999 次继续、随后 1 次退出,与条件结果独立且近似各占 50% 的判断,难度通常不同。前者有明显倾向,后者在缺少其他关联时很难仅凭过去判断下一次。所谓“50% 条件最难预测”必须带上这个前提:规则交替的真假序列也各占 50%,却存在可利用的规律。比例描述结果分布,预测难度还取决于排列和上下文。

写代码时给常见路径更紧凑的布局,有时能帮助指令供应和分支行为,但源代码里的“很可能”并不等于硬件接受了保证。编译器也可能把条件变成条件选择指令,使最终机器码没有对应的条件跳转。分析前应确认构建模式和实际生成的热点代码。先看条件的业务含义,再看编译后的控制流,才能把源代码直觉与硬件事件对应起来。

3 推测执行与错误路径回收

预测只给出候选路径,推测执行则让 CPU 沿候选路径继续做事。比如预测某条记录不会触发报警,后续的普通统计可能已经开始计算,甚至发起后续数据的合法读取。此时得到的是暂时结果。执行完成与退休不是同义词:退休表示指令满足处理器的提交条件,其效果按体系结构规定成为程序可见状态。这个区分是理解错误路径回收的关键。

当分支条件确定,处理器会核对真实方向和目标。如果预测正确,后续工作可以在满足其他条件后提交;如果预测错误,该分支之后较年轻的错误路径指令需要取消,取指转向真实地址。这里的“较年轻”指程序顺序上更靠后的指令。已经处于分支之前、与错误选择无关的工作不必因为这个分支全部重做。教学图只画单个未决分支,真实处理器可以同时跟踪更多工作。

推测结果核对与错误路径回收
推测结果核对与错误路径回收

回收不等于把整颗芯片恢复到过去某个完全相同的物理状态。错误路径产生的临时寄存器结果不能成为程序最终结果,存储也不能作为错误路径的程序写入任意对外提交;但缓存内容、预测状态等微架构状态可能受到影响。这也是理解相关安全问题所需的边界。本课只建立“程序结果被保护”和“所有内部痕迹消失”不同这层认识,不展开侧信道攻击、隔离策略或漏洞细节。

错误预测的损失包含走错路浪费的机会,以及重定向和恢复正确指令供应所需的时间。不能简单把公开资料里的流水线级数当成固定罚时。分支何时解析、目标是否及时取得、正确路径指令是否在缓存、其他指令能否重叠执行,都会影响实际损失。工程估算中常使用“每次误预测的有效额外周期”,它是特定条件下的近似平均量,而不是所有程序通用的常数。

推测执行值得采用,是因为很多常见分支具有可预测性,提前工作能减少等待。它也带来额外活动和功耗,猜错时消耗的工作可能没有退休。不要因此认为“退休指令数少了,芯片做的工作必然同比减少”。退休计数主要描述最终有效执行路径;观察能耗、温度或吞吐时,还要考虑未退休的内部活动,以及频率变化带来的影响。

4 动手:用分支密度估算气泡成本

先建立足够简单的估算模型。令分支密度为每条退休指令对应的分支数,误预测比例为误预测分支数除以同口径的分支数,有效代价为每次误预测带来的平均额外周期。则额外 CPI 约等于“分支密度 × 误预测比例 × 有效代价”。CPI 是每条退休指令的平均周期数;额外 CPI 与其余因素形成的基准 CPI 相加,可以得到用于比较的总 CPI 估计。

假设某段计算每 100 条退休指令有 20 条分支,其中 5% 误预测,每次有效代价取 15 周期。每 100 条指令约有 1 次误预测,额外消耗约 15 周期,所以额外 CPI 为 0.15。若基准 CPI 为 0.80,估算总 CPI 为 0.95。在固定 3.0 GHz 下,退休指令吞吐约为 3.0 ÷ 0.95,即每秒 3.16 十亿条。这不是每秒记录数,记录的平均指令成本还需要单独测量。

若误预测比例从 5% 增至 20%,其他假设保持不变,额外 CPI 变为 0.60,总 CPI 变为 1.40。这个算例说明误预测比例的变化会被分支密度放大。它并不证明任何真实代码改动都能保持其他因素不变:去掉分支可能增加指令数,分类数据可能增加搬运,改成查表也可能引入缓存未命中。模型用来判断量级和提出问题,收益最终由测量确定。

在 Linux 机器上,可以对已有扫描程序作初步观察。例如运行 perf stat -r 5 -e cycles,instructions,branches,branch-misses ./scan_records,让它重复处理固定输入。这里的 5 表示重复测量次数。若平台不支持事件或权限不足,应使用该平台可用的性能工具,不能把未采集到的数据记成 0。事件名称相似也不保证不同 CPU 的统计范围完全相同,跨平台比较前应查清事件定义。

准备 2 组记录:保持记录数、字段布局与命中比例接近,分别采用按条件分组和打乱的排列。尽量在计时前完成文件读取与输入准备,使初步实验聚焦内存中的扫描;同时记录工作集是否超出缓存。固定线程数和亲和设置,保留输出校验结果。先比较总时间、周期和指令,再看分支统计。冷缓存、调频、后台负载以及不同的数据访问顺序都可能影响结果,不能把排列变化的全部效果算成预测收益。

从固定输入到端到端收益的观察路径
从固定输入到端到端收益的观察路径

最后核对机器码是否确有目标分支。如果编译器已采用条件选择或向量化,实验测到的就不是预想的逐记录条件跳转。也不要用循环里的打印来“确认走向”,输出调用会显著改变工作负载。可以在计时区外校验累计值,或检查生成代码。加性模型若已经使用包含误预测的实测 CPI 作为基准,再叠加估算代价,就会重复计算同项损失。

5 间接分支与返回栈

条件分支通常在已知的候选路径之间选择方向。间接分支的目标则由运行时值给出,例如通过函数指针调用不同的数据解码函数。网卡接收队列里的记录可能有多种格式,程序读取类型字段,再调用对应解析函数。即使每条记录都会发生调用,目标也可能连续稳定,或者在多个函数之间快速切换。此时“跳不跳”的比例无法完整描述问题,还要判断目标地址是否容易预测。

固定目标的直接调用通常能从指令本身取得目标信息;间接调用需要预测运行时目标。若同处调用点长期只接收某类记录,目标相对稳定;若多种类型无规则混杂,预测可能更困难。多个目标还可能扩大活跃指令范围,增加指令缓存压力。因此分类后更快,并不能直接推出全部收益都来自目标预测;代码局部性和数据局部性也可能同时改变。

条件方向、间接目标与返回地址的区别
条件方向、间接目标与返回地址的区别

函数返回具有特殊规律:常规调用会确定之后应返回的位置,返回预测结构可以利用这种配对关系。概念上,每次调用记录返回地址,返回时取出最近匹配的候选,形状类似栈。因此常称为返回地址栈,或使用 RAS、RSB 等名称。它是处理器帮助预测的内部结构,与程序放在内存里的调用栈不是同件东西;预测候选仍需与实际返回行为核对。

这个结构有有限容量,实现也各不相同。较深的调用嵌套、递归或不规则控制转移,可能让简单配对直觉不再充分。不能据此规定“函数超过某个固定层数就一定慢”,更不能把某款处理器的内部容量推广到其他芯片。入门优化先观察热点调用形态和目标分布,避免为了猜测返回结构而大范围手工内联;内联也可能增大代码体积,带来其他代价。

对于服务器上的多类型记录处理,可以尝试在允许的范围内将相同类型集中,再按类型执行批处理。不过,原始顺序可能承载时间语义,批量等待可能增加延迟,队列也会占用内存。若处理次序必须保持,就需要额外索引或合并步骤。硬件喜欢的控制流安排必须服从程序正确性和延迟约束,不能只围绕预测事件计数作决定。

6 常见坑:用平均预测率掩盖热点分支

“全程序预测正确率达到 99%”不足以判断某段热点是否健康。假设某个循环执行了 900 万次很容易预测的分支,另个关键解析阶段执行了 100 万次分支,其中 10 万次误预测。按这组简化数据合并,误预测比例是 1%;解析阶段自身却是 10%。如果后者限制单条处理链的速度,全局平均会掩盖真正需要处理的位置。必须同时看次数、位置和阶段耗时。

百分比还隐藏了分母。每秒 100 万次分支中的 10% 误预测,与每秒 1 亿次分支中的 1% 误预测,对应的误预测次数分别是 10 万和 100 万。比例较低的阶段也可能付出更多总成本。实际对比时,可以记录每条记录的误预测次数、每条记录的周期数,以及该阶段占总时间的比例。不同指标互相补充,才能避免“比例变好但总吞吐下降”的误判。

另个常见错误是把所有难预测条件都改成无分支计算。例如原本只有少量记录需要进行较贵的校验,改写后每条记录都先计算校验,再根据条件选择结果。条件跳转可能减少了,但执行工作量增加了。若两侧含有访存,还可能读取原本无须访问的数据。条件选择也有依赖链成本,并非看见分支消失就必然加速。

更严重的是语义变化。原来的保护条件可能确保除数不为 0、索引不越界,或某个设备操作仅在允许时发生。不能把这些操作无条件执行,再用掩码隐藏结果。内存映射设备寄存器的读取可能具有副作用,不能当作普通数组读取随意搬动。CPU 自身的推测规则与源代码改写后的合法行为是不同层次,程序员必须维护语言规则和设备接口要求。

平均数据还可能混合预热、初始化、稳定处理与退出阶段。初始化里容易预测的大循环会稀释长期服务阶段的困难分支。测量时应明确计时区间,并覆盖正常输入、边界输入和异常突发。若生产环境的类型分布随时段变化,只在整齐样本上获得的高预测率无法证明长期收益。热点优化需要把输入分布当作实验条件记录下来。

7 案例:难预测分支拖垮吞吐

考虑 1 台配有 16 核 CPU、64 GB 内存、25 GbE 网卡的机架服务器,负责解析采集设备上传的固定长度记录。每条记录为 64 B,其中类型字段决定使用哪种简单累加规则。为定位计算阶段,先把 256 MiB 输入放入内存,使用单线程扫描并输出校验和。该工作集可能明显超出片上缓存,因此还需要观察内存停顿。机箱规格和网卡速率只是运行背景,不能据此认定瓶颈位置。

假设基线与候选版本在同次构建条件下,处理同样记录,测得基线每条记录约 24 条退休指令、30 周期、0.60 次分支误预测;候选约 28 条指令、23 周期、0.12 次误预测。这组数字用于说明分析方法,并非任何芯片的实测承诺。基线 CPI 为 1.25,候选约为 0.82,尽管候选指令更多,处理每条记录的周期反而降低。评价目标是每条有效记录的成本。

候选改动可以很小:把热点中的简单条件累加改成经编译器确认的条件选择形式,同时保留越界保护和输出语义。若每次误预测的有效代价暂取 15 周期,减少的 0.48 次误预测对应约 7.2 周期,与实测少用的 7 周期量级接近。这构成支持分支解释的证据,但仍不是严格归因证明;指令调度、寄存器使用和缓存行为也可能发生变化。

难预测热点的排查与保留条件
难预测热点的排查与保留条件

接下来更换输入分布。当类型长期相同、原分支很容易预测时,无分支版本可能没有收益,甚至因为多做运算而变慢。把记录按类型分组也可能有用,但应把分组、缓冲和结果恢复顺序的时间算进去。若必须等待凑满 4096 条记录才处理,吞吐提升可能伴随更长的等待时间;对于有时限的采集告警,这项代价必须单独评估。

再把候选放回真实接收流程。25 GbE 的名义线速除以 8 约为 3.125 GB/s,这只是未扣除协议等开销的换算;应用可用吞吐还受报文大小、复制和系统处理限制。若真实瓶颈是网卡接收或存储落盘,单线程扫描提速不会等比例提高总吞吐。若 16 核并行已经把共享内存带宽推高,单核收益也可能无法线性扩展。必须分别报告局部成本与整机结果。

合格的案例结论应说明:在何种记录分布、构建选项和线程配置下,哪处热点分支减少了多少误预测,每条记录的周期如何变化,端到端吞吐和尾延迟是否改善。若只能证明计算阶段更快,就如实限定范围。保留这个边界,比把所有性能变化归入“CPU 预测更准”更有助于后续维护。

8 把分支效应写进热点优化清单

清单首先记录工作单位。对记录扫描用“每条记录”,对压缩块处理用“每个输入块”,并给出大小与有效输出要求。然后记录 CPU 型号或架构信息、核心与线程设置、内存配置、操作系统、编译器和优化选项。涉及 NVMe 或网卡时,注明计时是否包含 I/O,以及走的是哪条接口路径。这样后续人员才知道测量针对计算、数据搬运还是整条处理链。

热点证据至少包含函数或代码位置、时间占比、退休指令、分支与误预测的统计口径。若工具只能给出近似归因,应明确其局限,不把采样落在某条指令上解释为精确因果。将全程序事件与热点阶段分开,检查分母是否一致。事件不支持、样本太少或结果波动很大时,应保留不确定性,先改善实验条件再作代码决定。

提出改动时写清作用机制,例如“减少不规则类型切换”“将简单条件累加变为条件选择”“将相同处理函数的记录组成批次”。同时列出可能增加的指令、内存流量、代码体积和排队等待。不要把“降低分支数”单独当作目标,因为某些原本可预测的分支几乎没有造成主要损失,删掉它们反而可能扩大成本。

正确性检查应覆盖正常值、边界值、非法输入及顺序要求,尤其是条件原本保护的读取和运算。性能检查则覆盖代表性分布与容易预测的对照分布,报告多次测量的波动和实际业务指标。若候选只在刻意随机样本上更快,应说明它适用的条件。优化清单的用途是帮助团队决定何时采用、何时回退,而不只是展示某次最好成绩。

最后设定保留标准:输出一致,目标阶段或端到端指标达到预期,尾延迟及资源成本处于允许范围。若误预测次数下降而总时间没有改善,也应记录结果;这可能说明分支不是主要限制,或收益被新增工作抵消。这样的失败实验同样有价值,它能阻止后续人员反复沿着缺乏收益的方向投入时间。

9 从控制流走向数据并行(H22)

当大量记录执行相似运算时,可以进一步问:是否必须逐条用控制流决定做什么?例如对连续温度数组计算超阈值计数,传统写法每次比较后决定是否增加计数;另种表达方式是先产生条件结果,再把满足条件的位置汇总。前者强调“走哪条路”,后者强调“哪些数据位置参与”。这种视角会自然连接到 H22 的 SIMD 与向量化。

SIMD 让单条指令对多个数据通道执行相似操作。条件可以形成掩码,表示哪些通道有效,但并非所有指令集对掩码和异常的处理方式都相同。本课先保留概念:把条件变成数据,有时能减少频繁的控制转移,并提高单位指令处理的数据量。实际是否可行,要看运算是否独立、数据布局是否合适,以及编译器能否生成有效代码。

从逐条条件判断到批量数据处理
从逐条条件判断到批量数据处理

这不是“预测不够好就必须向量化”的固定顺序。分支高度可预测、条件成立很少且后续操作昂贵时,保留分支可能更合适。记录结构里若只有少数字段参与运算,按字段组织的数据布局可能更便于连续读取,但转换布局也需要时间和额外空间。对零散指针结构,数据搬运往往比控制判断更棘手。应先确认目标阶段的主要成本,再选择表达方式。

进入 H22 前,可以带着这组问题看自己的热点:各条记录能否独立处理,计算是否相似,数据是否连续,条件是否能安全表示成掩码,批量大小是否满足延迟要求?同时保留本课的测量习惯,把退休指令数、每条记录周期与端到端指标放在同张记录表里。无论最终采用预测友好的控制流还是数据并行,目标都是以正确结果完成更多有效工作。

放进方案里

在服务器热点优化方案中,把分支效应写成可核对的假设。注明机器的 CPU、核心配置、内存容量与通道配置,说明输入来自内存回放、NVMe 读取还是网卡接收;列出记录大小、类型分布、处理顺序和线程数。若使用 25 GbE 网卡与 PCIe 4.0 NVMe,分别说明它们是否处于计时路径,并记录实际吞吐,避免用接口标称值代替测量。

基线部分记录每条记录的周期、退休指令和误预测次数,指出目标函数的耗时占比,说明性能事件的统计范围与工具限制。把全程序平均预测率作为背景,把热点分支的出现次数和输入关联作为判断依据。若工作集超过缓存或存在内存等待,应同时记录相关证据,防止把访存延迟误算成固定的分支罚时。

候选部分写出具体改动及其代价:采用条件选择、调整常见路径布局,或按类型形成批次。列明新增指令、临时缓冲、排序成本和最大等待时间,说明越界保护、异常处理与设备访问语义如何保持。涉及分组时,要确认记录是否允许重排,并将恢复输出顺序的成本计入总时间。

验收采用相同输入与构建条件,覆盖稳定类型、混合类型和异常突发,比较多次测量结果。附上热点机器码,确认条件跳转是否转为条件选择,间接调用目标是否减少;将每条记录的误预测次数与周期变化配对报告。预先约定周期改善门槛和尾延迟上限,若事件减少但周期未达标,不以预测率改善判定通过。只有正确性、目标吞吐及尾延迟同时满足约定,才保留改动。若计算阶段改善而整机收益有限,应明确当前瓶颈及收益范围。最终交付保留基线、候选、适用输入和回退条件,使后续更换 CPU 或业务分布后能够重新判断。

10 小结:下一步 SIMD 与向量化(H22)

分支让后续取指地址暂时不确定;预测提供候选地址,推测执行提前开展工作,核对失败后回收错误路径。预测率必须结合分支密度、有效恢复代价和热点位置理解,最终以每条有效记录的成本及整机指标判断收益。

进入 H22 时,把注意力从“下条执行路径在哪里”扩展到“同条指令能处理多少数据”。条件掩码、连续布局和批量处理会成为新工具,但正确性、输入分布与总成本仍是共同的验收依据。

同阶段相关课

AI 答疑

默认关闭。启用后,只发送本课标题、最近阅读的屏幕内正文摘录(最多 6000 字)与本次问题。

配置自己的服务