【问题标题】: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)。

注意:

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

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 时,它类似于插入排序中的场景,在这种情况下,稍后执行花费的时间更少。

【问题讨论】:

标签: java sorting time execution


【解决方案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 执行,从而显着提高性能。

【讨论】:

  • 这可能是一个原因,但是,当我尝试快速排序前几次时,反而花费更少的时间。可能还有其他更多原因?
猜你喜欢
  • 1970-01-01
  • 2021-12-18
  • 2020-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-06
相关资源
最近更新 更多