为啥程序的执行时间会发生显着变化?
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 是按值传递的,为啥我的对象在执行方法后会发生变化?
为啥执行两次 "a, x = x, a" 不会导致值发生变化?