第9章 优化错误投机

当经常发生分支预测错误时,会导致显著地性能下降。因为CPU需要清理前面已经完成但是后面被证明是错误的投机工作,刷新整条流水线,并用正确的指令填充它。通常,现代CPU发生分支预测错误时会有15~20时钟周期的开销。

对于常规应用程序,有5%~10%的“错误投机”率是正常的,当指标超过10%才值得关注。

也许,唯一可以直接解决分支预测错误的方法就是消除分支本身。如何使用查表和断言代替分支。

9.1 用查表替换分支

int mapToBucket(unsigned v) {
    if (v >= 0 && v < 10) return 0;
    if (v >= 10 && v < 20) return 1;
    if (v >= 20 && v < 30) return 2;
    if (v >= 30 && v < 40) return 3;
    return -1;
}

int buckets[256] = {0..., 1..., 2...,...};

int mapToBucket(unsigned v) {
    if (v < sizeof(buckets) / sizeof(int))
        return buckets[v];
    return -1;
}

如果需要映射较大的值范围,分配一个非常大的数组并不实用。此时使用间隔映射数组数据结构来实现该目标,其使用的内存更少但有着对数级查询复杂度。

9.2 用断言替换分支

通过执行分支的两条路径然后选取正确的结果(断言)可以有效消除分支。

int a;
if (cond) {
    a = X();
} else {
    a = Y();
}

// opt
int a = cond ? X() : Y();

消除了分支预测错误的损失,但是有可能会比原始代码做更多的工作。收益取决于多做的工作和一次分支判断比较。

此外,断言的问题在于它限制CPU的并行执行能力,限制了CPU投机执行的能力。二分搜索是一个检验断言替换分支的权衡选择的经典例子:
        1. 对于超过CPU缓存大小的大数组搜索场景,基于分支版本性能更好。因为分支预测错误的性能损失相比于内存延迟要小;
        2. 对于能全部填充到CPU缓存的小数组,情况正好相反。

再次强调,一定要测量确定替换分支是否是有收益的。

9.3 总结

        1. 仅在TMA报告显示错误投机指标高才尝试解决分支预测错误问题;
        2. 通过查表和断言替换分支是否有收益,一定要测试它是否真的更好。

你可能感兴趣的:(现代CPU性能分析与优化,Bakhvalov,性能优化,计算机体系结构)