java中相同代码块的运行时间不同。 这是为什么?

我有以下代码。 我只是想检查一下代码块的运行时间。 而且我错误地复制并粘贴了相同的代码并获得了一个有趣的结果。 虽然代码块是相同的,但运行时间是不同的。 并且code block 1比其他code block 1花费更多时间。 如果我切换代码块(say i move the code blocks 4 to the top)那么代码块4将比其他代码块花费更多时间。

我在代码块中使用了两种不同类型的数组来检查它取决于它。 结果是一样的。 如果代码块具有相同类型的数组,则最顶层的代码块需要更多时间。 请参阅以下代码和给定的输出。

 public class ABBYtest { public static void main(String[] args) { long startTime; long endTime; //code block 1 startTime = System.nanoTime(); Long a[] = new Long[10]; for (int i = 0; i < a.length; i++) { a[i] = 12l; } Arrays.sort(a); endTime = System.nanoTime(); System.out.println("code block (has Long array) 1 = " + (endTime - startTime)); //code block 6 startTime = System.nanoTime(); Long aa[] = new Long[10]; for (int i = 0; i < aa.length; i++) { aa[i] = 12l; } Arrays.sort(aa); endTime = System.nanoTime(); System.out.println("code block (has Long array) 6 = " + (endTime - startTime)); //code block 7 startTime = System.nanoTime(); Long aaa[] = new Long[10]; for (int i = 0; i < aaa.length; i++) { aaa[i] = 12l; } Arrays.sort(aaa); endTime = System.nanoTime(); System.out.println("code block (has Long array) 7 = " + (endTime - startTime)); //code block 2 startTime = System.nanoTime(); long c[] = new long[10]; for (int i = 0; i < c.length; i++) { c[i] = 12l; } Arrays.sort(c); endTime = System.nanoTime(); System.out.println("code block (has long array) 2 = " + (endTime - startTime)); //code block 3 startTime = System.nanoTime(); long d[] = new long[10]; for (int i = 0; i < d.length; i++) { d[i] = 12l; } Arrays.sort(d); endTime = System.nanoTime(); System.out.println("code block (has long array) 3 = " + (endTime - startTime)); //code block 4 startTime = System.nanoTime(); long b[] = new long[10]; for (int i = 0; i < b.length; i++) { b[i] = 12l; } Arrays.sort(b); endTime = System.nanoTime(); System.out.println("code block (has long array) 4 = " + (endTime - startTime)); //code block 5 startTime = System.nanoTime(); Long e[] = new Long[10]; for (int i = 0; i < e.length; i++) { e[i] = 12l; } Arrays.sort(e); endTime = System.nanoTime(); System.out.println("code block (has Long array) 5 = " + (endTime - startTime)); } } 

运行时间:

code block (has Long array) 1 = 802565

code block (has Long array) 6 = 6158

code block (has Long array) 7 = 4619

code block (has long array) 2 = 171906

code block (has long array) 3 = 4105

code block (has long array) 4 = 3079

code block (has Long array) 5 = 8210

正如我们所看到的,包含Long array的第一个代码块将比包含Long arrays其他代码块花费更多时间。 对于包含long array的第一个代码块,它是相同的。

谁能解释这种行为。 或者我在这里犯了一些错误?

基准测试错误。 什么是错误的非详尽列表:

  • 没有预热 :单次测量几乎总是错误的;
  • 在单个方法中混合使用几个代码路径 :我们可能开始使用仅适用于方法中第一个循环的执行数据来编译方法;
  • 来源是可预测的 :如果循环编译,我们实际上可以预测结果;
  • 结果是消除了死代码 :如果循环编译,我们可以抛弃它

以下是jmh如何做到这一点 :

 @OutputTimeUnit(TimeUnit.NANOSECONDS) @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 3, time = 1) @Measurement(iterations = 3, time = 1) @Fork(10) @State(Scope.Thread) public class Longs { public static final int COUNT = 10; private Long[] refLongs; private long[] primLongs; /* * Implementation notes: * - copying the array from the field keeps the constant * optimizations away, but we implicitly counting the * costs of arraycopy() in; * - two additional baseline experiments quantify the * scale of arraycopy effects (note you can't directly * subtract the baseline scores from the tests, because * the code is mixed together; * - the resulting arrays are always fed back into JMH * to prevent dead-code elimination */ @Setup public void setup() { primLongs = new long[COUNT]; for (int i = 0; i < COUNT; i++) { primLongs[i] = 12l; } refLongs = new Long[COUNT]; for (int i = 0; i < COUNT; i++) { refLongs[i] = 12l; } } @GenerateMicroBenchmark public long[] prim_baseline() { long[] d = new long[COUNT]; System.arraycopy(primLongs, 0, d, 0, COUNT); return d; } @GenerateMicroBenchmark public long[] prim_sort() { long[] d = new long[COUNT]; System.arraycopy(primLongs, 0, d, 0, COUNT); Arrays.sort(d); return d; } @GenerateMicroBenchmark public Long[] ref_baseline() { Long[] d = new Long[COUNT]; System.arraycopy(refLongs, 0, d, 0, COUNT); return d; } @GenerateMicroBenchmark public Long[] ref_sort() { Long[] d = new Long[COUNT]; System.arraycopy(refLongs, 0, d, 0, COUNT); Arrays.sort(d); return d; } } 

......这会产生:

 Benchmark Mode Samples Mean Mean error Units osLongs.prim_baseline avgt 30 19.604 0.327 ns/op osLongs.prim_sort avgt 30 51.217 1.873 ns/op osLongs.ref_baseline avgt 30 16.935 0.087 ns/op osLongs.ref_sort avgt 30 25.199 0.430 ns/op 

此时你可能会开始想知道为什么排序Long[]和排序long[]需要不同的时间。 答案在于Array.sort()重载:OpenJDK通过不同的算法对原始和引用数组进行排序(使用TimSort引用,带有双枢轴快速排序的基元)。 以下是使用-Djava.util.Arrays.useLegacyMergeSort=true选择另一个算法的重点,它回退到引用的合并排序:

 Benchmark Mode Samples Mean Mean error Units osLongs.prim_baseline avgt 30 19.675 0.291 ns/op osLongs.prim_sort avgt 30 50.882 1.550 ns/op osLongs.ref_baseline avgt 30 16.742 0.089 ns/op osLongs.ref_sort avgt 30 64.207 1.047 ns/op 

希望有助于解释差异。

上面的解释几乎没有涉及排序性能的表面。 当呈现不同的源数据(包括可用的预先排序的子序列,它们的模式和运行长度,数据本身的大小)时,性能是非常不同的。

谁能解释这种行为。 或者我在这里犯了一些错误?

你的问题是一个写得不好的基准。 您没有考虑JVM预热效果。 诸如加载代码的开销,堆的初始扩展和JIT编译之类的东西。 此外,应用程序的启动总是会产生需要收集的额外垃圾。

此外,如果您的应用程序本身生成垃圾(我希望sort和/或println正在执行此操作),那么您需要在基准应用程序运行的“稳定状态”阶段考虑可能的GC运行。

有关如何编写有效 Java基准测试的提示,请参阅此问答:

  • 如何在Java中编写正确的微基准测试?

关于此,还有许多其他文章。 Google为“如何编写java基准”。


在这个例子中,我怀疑第一个代码块比其余代码块花费的时间长得多,因为(最初)字节码解释后面是JIT编译的开销。 您可能正在进行垃圾收集以处理在加载和JIT编译期间创建的临时对象。 第四次测量的高值很可能是由于另一个垃圾收集周期。

但是,需要打开一些JVM日志记录才能找出真正的原因。

只是为了补充其他人的意思。 Java不一定会编译所有内容。 当它分析优化代码时,java将选择解释在相当长的时间内没有广泛使用的代码。 如果你看一下字节码你的Long数组应该总是比你的长数组花费更多的时间和空间复杂性,但正如已经指出的那样,预热效果会产生影响。

这可能是由于以下几点:

  • 正如syrion所指出的,Java的虚拟机可以在代码运行时对其进行优化。 您的第一个块可能需要更长的时间,因为Java还没有完全优化您的代码。 当第一个块运行时,JVM正在应用更改,然后可以在其他块中使用这些更改。
  • 您的处理器可以缓存代码的结果,从而加速未来的块。 这与前一点类似,但即使在相同的JVM实现之间也可能有所不同。
  • 在程序运行时,您的计算机也在执行其他任务。 这些包括处理操作系统的UI,检查程序更新等。因此,某些代码块可能比其他代码块慢,因为您的计算机并没有将大量资源集中用于执行。
  • Java的虚拟机是垃圾收集的 。 也就是说,在程序执行期间的未指定点,JVM需要一些时间来清理不再使用的任何对象。

第1点和第2点可能是第一个块执行时间差异较大的原因。 第3点可能是波动较小的原因,而斯蒂芬指出,第4点可能导致第3区的大失速。

我没注意到的另一件事是你使用longLong 。 对象forms包含更大的内存开销,并且两者都受到不同的优化。