分支预测与乱序执行:现代 CPU 的性能技巧
前一篇讲流水线冒险时已经看到,真正拖慢现代 CPU 的,很多时候不是“不会执行指令”,而是:
- 后面的指令在等前面的结果;
- 前端不知道分支该往哪走;
- 某些阶段被迫空转;
- 硬件资源没有被尽量榨干。
于是现代 CPU 为了继续提高性能,就不能只满足于“有流水线”,而是必须进一步想办法:
尽量提前猜、尽量少等、尽量让能先做的先做。
这就引出了两个很关键的现代处理器技巧:分支预测和乱序执行。
一、为什么现代 CPU 需要更激进的优化
单纯的流水线只能解决“阶段重叠”问题,但一旦程序里有大量分支、依赖和内存访问延迟,理想流水线就会频繁被打断。
如果 CPU 每次都老老实实地等:
- 等分支结果出来再决定下一步;
- 等上一条结果写回再继续;
- 等一次慢访存彻底完成;
那很多硬件资源就会白白闲着。
所以现代 CPU 的目标变成了:
- 尽量让前端不停下来;
- 尽量让后端功能部件别闲着;
- 尽量把可并行的指令提前挖出来。
这就是后面这些优化存在的根本动机。
二、分支预测为什么这么重要
因为控制冒险太频繁了。
程序里几乎到处都是:
if/elsefor/while- 函数调用返回
- 异常和跳转
每遇到一次分支,CPU 理论上都得先知道结果,才能确定下一条该取哪条指令。但如果等结果出来再行动,前端就会停顿,吞吐大幅下降。
所以现代 CPU 通常会选择一个更积极的策略:
先猜。
三、什么是分支预测
分支预测的核心思想很简单:
在分支真正算出结果之前,CPU 先预测下一条会走哪条路径,并按预测结果提前取指、译码甚至执行。
如果猜对了,就能省掉大量等待时间;如果猜错了,再把错误路径上的工作丢弃,重新走正确路径。
所以分支预测本质上是一种用“可能的浪费”换“平均性能提升”的策略。
四、为什么分支预测往往是值得的
因为很多程序分支并不是完全随机的。
例如:
- 循环里的分支通常大部分时间都会继续循环;
- 某些条件判断在真实负载下往往长期偏向某一边;
- 函数返回和某些固定模式的控制流具有明显规律。
也就是说,程序行为常常是有历史模式可学的。只要预测器抓住这些模式,预测命中率就会相当可观。
这也是为什么现代 CPU 会投入大量硬件去做分支预测。
五、分支预测常见有哪些层次
1. 静态预测
比较简单,例如默认“不跳转”,或者根据分支方向做固定猜测。
优点是实现简单;缺点是适应性弱。
2. 动态预测
根据分支过去的实际行为来预测未来,例如记录某个分支最近是跳还是不跳。
这类方法更符合程序运行的现实模式,所以现代 CPU 里更常见。
3. 更复杂的历史相关预测
会结合更长的局部或全局历史,尝试识别更复杂的行为模式。
这里不必一开始就记各种具体预测器名字,先抓住主线即可:
预测器本质上是在学习控制流历史规律。
六、分支预测失败为什么代价很高
因为现代流水线前端往往已经往错误方向走了好几步,甚至后端也做了不少投机工作。
一旦猜错,就需要:
- 清空错误路径上的若干指令;
- 恢复正确的
PC; - 重新取指、重新填充流水线。
这会带来明显的性能损失。
所以分支预测不是“有没有用”的问题,而是“预测准确率够不够高”的问题。准确率越高,流水线前端越顺畅。
七、什么是乱序执行
乱序执行的核心思想也很直观:
只要不破坏程序最终语义,就不一定非得严格按程序书写顺序让每条指令完成执行。
也就是说,如果前面某条指令因为数据依赖或慢访存卡住了,而后面有一些互不依赖的指令其实已经准备好了,那 CPU 可以先让这些后面的指令去执行。
这样就能避免后端功能部件闲着等。
八、为什么乱序执行能提升性能
因为真实程序中,书写顺序和“可执行顺序”并不总是一回事。
例如:
- 某条加载指令在等内存;
- 但后面某条纯整数运算和它没关系;
- 如果非得顺序等着,ALU 会空着;
- 如果允许先做后面那条,就能把空闲时间利用起来。
所以乱序执行本质上是在挖掘指令级并行性,也就是 ILP。它想做的事情可以概括成一句话:
不要让本可以先做的工作,陪着别人一起傻等。
九、乱序执行会不会破坏程序正确性
这是最关键的问题。答案是:
- 内部执行顺序可以乱;
- 但对外可见结果必须保持和程序语义一致。
也就是说,现代 CPU 虽然可能先把后面某条无关指令做了,但最终提交结果时,仍然需要确保外部观察到的行为和程序顺序模型一致。
所以乱序执行并不是“随便乱来”,而是:
在保持正确性的前提下,内部尽量灵活安排执行先后。
十、为什么乱序执行通常要配合寄存器重命名等机制
因为如果只是字面上看寄存器名,很多原本不冲突的指令也可能看起来“像有依赖”。
例如某些指令只是暂时用了同一个寄存器名,但并没有真实的数据流依赖。
这时候现代 CPU 会通过寄存器重命名等方法,把逻辑寄存器和物理寄存器映射分开,从而:
- 消除一些伪相关;
- 释放更多可并行执行空间;
- 提高乱序调度效果。
这部分机制确实更深入,但至少要先建立一个认识:
乱序执行不是单点技巧,而是一套围绕依赖分析、调度和结果提交展开的协同体系。
十一、分支预测和乱序执行之间是什么关系
它们其实很互补。
- 分支预测主要解决“前端不知道该往哪取”的问题;
- 乱序执行主要解决“后端明明有事可做却被顺序卡住”的问题。
一个偏向保持取指和解码不断流;一个偏向让执行单元尽量别闲着。
所以现代 CPU 高性能的关键,往往就在于:
- 前端尽量猜得准;
- 后端尽量调得开;
- 中间再配合流水线、缓存和依赖处理一起工作。
十二、投机执行和它们有什么关系
很多时候,分支预测和乱序执行都会伴随投机执行这个概念。
它的核心意思是:
在还不完全确定某些条件是否成立之前,先按一个合理假设继续往前做。
例如:
- 分支预测猜对路径后,后续指令先跑起来;
- 某些结果先算出来暂存;
- 如果最后发现假设正确,就直接受益;
- 如果假设错误,就撤销相关结果。
所以投机执行其实是现代 CPU 提高平均性能的一种通用思路,而分支预测就是最经典的投机场景之一。
十三、从工程视角看,这部分为什么重要
即使你不设计处理器,理解这些机制也很有价值。
例如:
- 你会更理解为什么分支模式会影响性能;
- 你会更理解为什么简单、连续、可预测的代码更容易跑得快;
- 你会更理解 CPU 优化为什么不只是提主频,而是提升平均执行效率;
- 你会更理解现代性能分析里为什么总会提到 branch miss、speculation、frontend stall、backend stall 这些概念。
十四、学习这一部分最容易踩的坑
1. 以为分支预测只是“小优化”
实际上它对现代 CPU 性能影响非常大。
2. 把乱序执行理解成“程序真的乱了”
内部可乱,最终可见行为仍然必须满足程序语义。
3. 把这些技巧看成彼此孤立
真实 CPU 中,流水线、预测、乱序、缓存、依赖处理几乎都是联动的。
总结
分支预测和乱序执行,是现代 CPU 为了继续提升平均性能而发展出的关键技巧。真正值得先抓住的,是这些认识:
- 分支预测通过提前猜测控制流方向,减少前端停顿;
- 乱序执行通过提前执行无关指令,减少后端空闲;
- 它们本质上都在努力对抗流水线里的等待和不确定性;
- 现代 CPU 的高性能,很大程度上来自这种“尽量不停、尽量少等”的激进调度思想;
- 真正的难点不是会不会猜、会不会乱,而是在保持正确性的前提下把这些技巧协同起来。
把这一篇理解透之后,下一步再看性能指标和存储层次,就会更容易把现代 CPU 的很多现象放回一个统一框架里理解:真正的性能,来自吞吐、预测、并行、访存和执行效率共同作用的结果。
参考资源:
- 《深入理解计算机系统》
- 《计算机组成与设计:硬件/软件接口》
- Wikipedia - Branch predictor
- Wikipedia - Out-of-order execution