数组排序前后的执行效率区别

本文探讨了通过排序数组提升分支预测准确率,从而显著提高程序执行效率的现象。详细解释了分支预测原理及其在处理器级的应用,包括火车比喻、if语句预测与回滚等概念,最终提出了解决方案和优化建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有如下代码:

import java.util.Arrays;
import java.util.Random;

public class Main
{
    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
        <strong>Arrays.sort(data);</strong>

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

        for (int i = 0; i < 100000; ++i)
        {
            // Primary loop
            for (int c = 0; c < arraySize; ++c)
            {
<span style="color:#ff0000;">               </span><strong style="color: rgb(255, 0, 0);"> if (data[c] >= 128)</strong><span style="color:#ff0000;">
                  </span><strong><span style="color:#ff0000;">  </span>sum += data[c];</strong>
            }
        }

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

排序后的数组,执行效率与未排序时有明显区别,我本地执行时,前者是5s,后者为11s。


这是一个典型的“分支预测”问题。

假设你是18世纪的扳道工,这时远程通信技术并不发达。你并不知道每辆火车的方向,所以每当火车遇到岔道口,需要先停下来告知驶向的方向并进行调整,然后才可以启动。笨重的火车具有很大的惯性,所以需要频繁的停下启动。

有什么好的方法?你来预测火车前进的方向!

1.预测成功,火车直接前进即可。

2.预测失败,停下、换道、重启。

如果你每次都预测成功了,那么火车中途就不需要停顿了。

然而你经常预测失败,所以大量的时间被耗费在刹车、重启上。。


在处理器级别,if是典型的分支预测指令。


当程序在处理器中执行时,只有走到那一步才知道判断的结果,现代的处理器结构复杂,所以经常需要刹车、重启。

有更好的方法么?处理器来预测分支的走向!

1.预测成功,继续执行。

2.预测失败,回滚、重新执行。

如果处理器每次都预测成功,那么处理器不会回滚。

然而处理器经常预测失败,所以耗费了大量的时间在回滚上。


这就是所谓的“分支预测”,可能这个比喻并不是十分的贴切——火车可以在车头高高的插一面旗帜表示方向,但是在处理器中,直到最后一刻处理器才会知道执行的方向。


处理器如何才能最少化回滚的次数呢?——以史为镜。如果前面99次火车都是朝左开,那么第一百次也会选择左边;如果是轮流的,那么下一次跟前一次就是相反的;如果每三次改变方向,同上。。。

简而言之,处理器试图总结一个模型,并遵循这个模型。


大部分的应用程序运行时都很有规律,领先的分支预测模型甚至能够达到90%以上的命中率,但是遇到难以预测的随机情况,分支预测机制也无能为力。


回头看上面的程序,可知造成效率差异的罪魁祸首是那个if判断,在预先没有对数组进行排序时,分支预测失效,导致处理器频繁的rollback;而在排序后,处理器很容易就找到了适用的预测模型,大大提高了效率。


解决方案:

1.在if语句之前加上排序。

2.避免分支预测,即使用位运算来替代if语句(牺牲可读性):

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

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];
即可。注意:如上代码并不适用所有环境。

验证结果稳定在5.8s。


扩展阅读:"Branch predictor" article on Wikipedia


原文地址:http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值