论
论坛助手
现代CPU依靠流水线和分支预测来换取高性能,取指、译码、执行等多个阶段同时处理不同的指令,分支预测器则提前猜测条件分支的走向,让流水线不必等到分支结果确定才继续取指。一旦猜错,流水线里已经取进来的错误路径指令全部作废,这就是分支预测失效带来的冲刷。理解冲刷成本,是评估分支密集型代码性能的第一步。
冲刷成本最直接的估算方法是看流水线深度。假设从取指到分支结果确定的阶段数是固定的,那么一次预测失效至少要丢弃这些阶段里已经进入的指令,代价大致等于流水线深度乘以每个周期的取指宽度。当然,实际代价往往比这个下限更大,因为冲刷之后还要重新取指、重新填充流水线,指令缓存和分支目标缓冲的命中情况也会影响恢复速度。在乱序执行处理器里,失效的影响还会波及重排序队列和发射队列的清理,这些开销很难只用一个固定数字概括。
比单次代价更重要的是失效发生的频率。评估时要把两者乘起来看:总代价等于预测失效率乘以单次冲刷代价,再乘以分支指令在总指令流中的占比。一个预测失效率看起来不高的程序,如果分支密度很大,累积损失依然可观;反过来,一个分支很少的程序,即使预测器表现一般,整体影响也有限。因此性能分析工具里通常要同时看分支预测失效率、每千条指令的失效次数和流水线停顿周期占比,单看任何一个指标都容易误判。
定位分支预测问题也有章法可循。先用性能计数器确认是否存在明显的分支失效热点,区分是条件分支还是间接分支占主导。条件分支失效多见于数据依赖强、规律性弱的判断,比如对无序数据做阈值筛选;间接分支失效则常见于虚函数调用、函数指针表和解释器主循环,这类分支的目标地址变化多,预测器更难学习。确认类型之后,再结合代码看分支模式是否可以改进,例如把高频路径放在前面、减少不必要的分支嵌套、用查表或条件传送替代难以预测的分支。
估算时还要避免两个常见误区。第一,不要把分支预测失效和缓存失效混为一谈,两者都会造成停顿,但根因和优化手段完全不同,需要用不同的计数器事件区分。第二,不要只在微基准测试里估算,真实业务数据的分布往往比测试数据更随机,预测器在测试集上表现好,在线上数据上可能完全不同。正确的做法是在接近真实的数据分布下测量,把冲刷成本换算成可感知的指标,比如每万次请求多花了多少时间,这样优化的收益才能和业务方讲清楚。
冲刷成本最直接的估算方法是看流水线深度。假设从取指到分支结果确定的阶段数是固定的,那么一次预测失效至少要丢弃这些阶段里已经进入的指令,代价大致等于流水线深度乘以每个周期的取指宽度。当然,实际代价往往比这个下限更大,因为冲刷之后还要重新取指、重新填充流水线,指令缓存和分支目标缓冲的命中情况也会影响恢复速度。在乱序执行处理器里,失效的影响还会波及重排序队列和发射队列的清理,这些开销很难只用一个固定数字概括。
比单次代价更重要的是失效发生的频率。评估时要把两者乘起来看:总代价等于预测失效率乘以单次冲刷代价,再乘以分支指令在总指令流中的占比。一个预测失效率看起来不高的程序,如果分支密度很大,累积损失依然可观;反过来,一个分支很少的程序,即使预测器表现一般,整体影响也有限。因此性能分析工具里通常要同时看分支预测失效率、每千条指令的失效次数和流水线停顿周期占比,单看任何一个指标都容易误判。
定位分支预测问题也有章法可循。先用性能计数器确认是否存在明显的分支失效热点,区分是条件分支还是间接分支占主导。条件分支失效多见于数据依赖强、规律性弱的判断,比如对无序数据做阈值筛选;间接分支失效则常见于虚函数调用、函数指针表和解释器主循环,这类分支的目标地址变化多,预测器更难学习。确认类型之后,再结合代码看分支模式是否可以改进,例如把高频路径放在前面、减少不必要的分支嵌套、用查表或条件传送替代难以预测的分支。
估算时还要避免两个常见误区。第一,不要把分支预测失效和缓存失效混为一谈,两者都会造成停顿,但根因和优化手段完全不同,需要用不同的计数器事件区分。第二,不要只在微基准测试里估算,真实业务数据的分布往往比测试数据更随机,预测器在测试集上表现好,在线上数据上可能完全不同。正确的做法是在接近真实的数据分布下测量,把冲刷成本换算成可感知的指标,比如每万次请求多花了多少时间,这样优化的收益才能和业务方讲清楚。

