经典问题之「分支预测」

问题

来源 :stackoverflow
为什么下面代码排序后累加比不排序快?

public static void main(String[] args) {
        // Generate data
        int arraySize = 32768;
        int data[] = new int[arraySize];

        Random rnd = new Random(0);
        for (int c = 0; c < arraySize; ++c)
            data[c] = rnd.nextInt() % 256;

        // !!! With this, the next loop runs faster
        Arrays.sort(data);

        // Test
        long start = System.nanoTime();
        long sum = 0;

        for (int i = 0; i < 100000; ++i)
        {
            // Primary loop
            for (int c = 0; c < arraySize; ++c)
            {
                if (data[c] >= 128)
                    sum += data[c];
            }
        }

        System.out.println((System.nanoTime() - start) / 1000000000.0);
        System.out.println("sum = " + sum);
    }

在我电脑上没有排序耗时:10.78390589
排序后耗时:4.552145206

出现上面这个时长差异的罪魁祸首就是这段代码 :

if (data[c] >= 128)
    sum += data[c];

排序后数据的示例:

T = 表示进入分支
N = 表示未进入分支

data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ...
branch = N  N  N  N  N  ...   N    N    T    T    T  ...   T    T    T  ...

 = NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT  (容易去预测)

没有排序数据的示例:

data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118,  14, 150, 177, 182, 133, ...
branch =   T,   T,   N,   T,   T,   T,   T,  N,   T,   N,   N,   T,   T,   T,   N  ...

  = TTNTTTTNTNNTTTN ...   (全是随机数据 - 很难去预测)

假如我们把代码里条件判断换成下面代码:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

没有排序耗时:2.698193263
排序后耗时 :2.753661927
说明没有用到条件判断语句没有排序和排好序的耗时很相近。
在现代处理器中,都引入了分支预测来提高指令流水线的性能。所以就导致排序后比没有排序快。

分支预测

条件分支指令通常具有两路后续执行分支。即不采取(not taken)跳转,顺序执行后面紧挨JMP的指令;以及采取(taken)跳转到另一块程序内存去执行那里的指令。
是否需要跳转,只有到真正执行时才能确定。如果没有分支预测器,处理器将会等待分支指令通过了指令流水线的执行阶段,才把下一条指令送入流水线的第一个阶段—取指令阶段(fetch stage),这种技术叫做 流水线停顿
分支预测器就是猜测条件判断会走哪一路,如果猜对,就避免流水线停顿造成的时间浪费。如果猜错,那么流水线中推测执行的那些中间结果全部放弃,重新获取正确的分支路线上的指令开始执行,这导致了程序执行的延迟。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 背景 条件分支指令通常具有两路后续执行分支。即不采取(not taken)跳转,顺序执行后面紧挨JMP的指令;以及...
    None_Ling阅读 33,299评论 1 7
  • 计算机系统漫游 代码从文本到可执行文件的过程(c语言示例):预处理阶段,处理 #inlcude , #defin...
    willdimagine阅读 3,648评论 0 5
  • RISC和CISC CPU的主要功能是通过执行各种指令实现的,CPU支持的所有指令构成了这个CPU的指令集。其中指...
    ye2012阅读 578评论 0 1
  • 对于同级别的CPU产品而言,AMD CPU的单核性能(甚至总体性能)比Intel CPU的差,甚至差距不小,这是不...
    PixelTogether阅读 5,896评论 0 8
  • 锯齿练习--闪电和王冠 今天的内容相对简单。 虽然简单,仍有学问。第一次凭印象和用笔习惯画王冠,线条画的是直的,感...
    春风和煦2020阅读 166评论 0 0