为啥程序的执行时间会发生显着变化?

Posted

技术标签:

【中文标题】为啥程序的执行时间会发生显着变化?【英文标题】:Why the execution time of a program changes significantly?为什么程序的执行时间会发生显着变化? 【发布时间】:2014-09-21 20:45:34 【问题描述】:

我想测试不同排序算法的执行时间,我发现了一个有趣的问题。当我多次运行程序时,比如说插入排序,前一两次比后面的时间花费更多的时间。这种情况发生在数组的大小很大时,不同的大小对执行时间的影响也不同。

public static void insertSort(int[] array)
    for(int i = 1; i<array.length; i++)
        int current = array[i];
        int j = i-1;
        while((j>=0)&&(array[j]>current))
            array[j+1] = array[j];
            array[j] = current; 
            j--;
        
    


public static void multiTimes(int size)
    Random r = new Random();    
    int a[] = new int[size];
    int b[] = new int[size];
    for(int j = 0; j<size; j++)
        a[j] = r.nextInt(size); 

    long startTime, endTime = 0;

    b = Arrays.copyOf(a, a.length);
    startTime=System.nanoTime();  
    insertSort(b);
    endTime=System.nanoTime();  
    System.out.println("Insert  "+(endTime-startTime)+"   ns"); 

    b = Arrays.copyOf(a, a.length);
    startTime=System.nanoTime();  
    insertSort(b);
    endTime=System.nanoTime();  
    System.out.println("Insert  "+(endTime-startTime)+"   ns"); 

    b = Arrays.copyOf(a, a.length);
    startTime=System.nanoTime();  
    insertSort(b);
    endTime=System.nanoTime();  
    System.out.println("Insert  "+(endTime-startTime)+"   ns"); 

    b = Arrays.copyOf(a, a.length);
    startTime=System.nanoTime();  
    insertSort(b);
    endTime=System.nanoTime();  
    System.out.println("Insert  "+(endTime-startTime)+"   ns"); 

    b = Arrays.copyOf(a, a.length);
    startTime=System.nanoTime();  
    insertSort(b);
    endTime=System.nanoTime();  
    System.out.println("Insert  "+(endTime-startTime)+"   ns"); 

    b = Arrays.copyOf(a, a.length);
    startTime=System.nanoTime();  
    insertSort(b);
    endTime=System.nanoTime();  
    System.out.println("Insert  "+(endTime-startTime)+"   ns");

尺寸:100 插入 77908 ns 插入 82573 ns 插入 75109 ns 插入 76508 ns 插入 91902 ns 插入 78840 ns

每次执行时间都差不多。

尺寸:1000: 插入 6256400 ns 插入 5674659 ns 插入 188938 ns 插入 188004 ns 插入 187071 ns 插入 186605 ns

尺寸:2000: 插入 7961037 ns 插入 6590889 ns 插入 793538 ns 插入 793072 ns 插入 793072 ns 插入 792138 ns

我们可以看到,对于 1000、2000 或更大的大小,结果是相当有趣的。前两次的执行时间大约是后面执行的30倍(size = 1000)。

注意:

    语言:Java JDK7; IDE:日食;平台:Win8.1; 对于每种尺寸,都进行了许多实验,结果非常相似。虽然执行时间有一定的随机性,但无法解释为什么前两次相似,比后面的时间长 30 倍以上。 一个可能的原因可能是阵列已经在数据缓存中,因此以后的执行花费的时间更少。我不确定是否还有其他原因。

PS: 在我测试了插入排序之后,我发现它在快速排序中甚至令人困惑。

public static void quickSort(int a[], int left, int right)
    if(right<=left)
        return;
    int temp[] = new int[right-left+1];
    for(int i = left; i<=right; i++)
        temp[i-left] = a[i];
    int pivot = a[left];
    int subr = right, subl = left;
    for(int i = left+1; i<=right;i++)
        if(temp[i-left]>pivot)
            a[subr--] = temp[i-left];
        else
            a[subl++] = temp[i-left];
    
    a[subl] = pivot;
    quickSort(a, left, subl-1);
    quickSort(a, subr+1, right);

大小 = 1000: QS 888240 ns 问题 2218734 ns 问题 2179547 ns 问题 2132896 ns QS 2146890 ns QS 2212670 ns

大小 = 500: QS 432924 ns QS 406799 ns QS 941889 ns QS 1103302 ns QS 1101436 纳秒 Qs 1086042 ns

当大小在 [200, 2000] 左右时,前几次比后面几次花费的时间更少,这与插入排序相反。当大小增加到超过 2000 时,它类似于插入排序中的场景,在这种情况下,稍后执行花费的时间更少。

【问题讨论】:

不是完全重复,但非常密切相关的是这个问题:How do I write a correct micro-benchmark in Java? 【参考方案1】:

当您删除排序方法的完整方法体并使用当前代码调用它时, 你会注意到同样的效果——在较小的范围内:

Insert  1488   ns
Insert  353   ns
Insert  246   ns
Insert  240   ns
Insert  224   ns
Insert  212   ns

如果您现在要同时删除属性int[] array,您仍然会注意到相同的效果:

Insert  1452   ns
Insert  342   ns
Insert  232   ns
Insert  203   ns
Insert  228   ns
Insert  209   ns

因此,显然这种行为与数据(-sate)、内存分配或内存中已经存在的某些值的重复无关。

显然,只有方法存根

public static void insertSort()


左,它需要与方法声明本身有关。正如 AlexR 已经说过的,Java 有一个 JIT 编译器。而且由于数据没有任何剩余,这种行为可能只有一个原因:运行时优化。

Java 是编译代码,这意味着在构建应用程序时,编写的 Java-Soure 被编译为低级语言。 在编译语言时,可以有不同的抽象步骤。每个字符都需要(最终)从人类可读的代码转换为“零”和“一” - 中间有取决于语言的层数。 由于您在设计时不知道运行时数据,因此无法将其转换为 1 和 0 - 因此代码介于两者之间。 (但它可以在运行时进一步翻译,当您最终知道数据并使用相同的数据重复访问相同的方法时!) 每种语言都有一个共同点:相同的输入等于相同的输出。 因此,每一层都可能有自己的(内部)缓存,以加快速度并减少 CPU/内存负载。

就像你可以在java中重用一个对象以避免从数据库重新加载一样,中间的每一层都可以重用已经使用的位和字节。

(从数据库的角度来看这个效果会引发同样的问题:为什么第一次显示字符串需要 125ms,而每隔一次只需要 5ms?)


想象一个有 10 人的房间,你问一个人:这里的平均年龄是多少? - 这个人需要问每个人的年龄,进行一些计算才能回答25.

如果您再次询问 - 没有任何改变 - 答案将立即出现。 (复制阵列将是一个房间开关,同时保持相同的人)

但是,如果您要更改人员(无论是保留还是更改房间)- 需要再次执行整个算法。

而且这个例子中间只有一层(被问者)可能记得已经问过的问题。

【讨论】:

谢谢。但是如何解释快速排序呢?前几次测试花费的时间更少。【参考方案2】:

可能有很多原因,但在您的情况下,我相信这是 JIT(即时编译)的效果,它编译为本机代码最近使用的字节码片段。这就是前 2 次执行速度较慢的原因。它们由解释 java 字节码的 JVM 完成。然后 JIT 将您的排序算法编译为本机代码,并由 JVM 执行,从而显着提高性能。

【讨论】:

这可能是一个原因,但是,当我尝试快速排序前几次时,反而花费更少的时间。可能还有其他更多原因?

以上是关于为啥程序的执行时间会发生显着变化?的主要内容,如果未能解决你的问题,请参考以下文章

如果 java 是按值传递的,为啥我的对象在执行方法后会发生变化?

为啥while循环的执行速度会随时间变化?

为啥在 c# 中重用数组会显着提高性能?

为啥执行两次 "a, x = x, a" 不会导致值发生变化?

如果我为我的表命名,为啥 SQL 查询顺序会发生变化? [关闭]

为啥调试时生成的程序退出代码会发生变化?