声明64个元素的多个数组比声明65个元素的数组快1000倍

时间:2013-09-15 08:38:28

标签: java arrays

最近我注意到声明一个包含64个元素的数组比用65个元素声明相同类型的数组快得多(> 1000倍)。

以下是我用来测试此代码的代码:

public class Tests{
    public static void main(String args[]){
        double start = System.nanoTime();
        int job = 100000000;//100 million
        for(int i = 0; i < job; i++){
            double[] test = new double[64];
        }
        double end = System.nanoTime();
        System.out.println("Total runtime = " + (end-start)/1000000 + " ms");
    }
}

这大约需要6毫秒,如果我用new double[64]替换new double[65],大约需要7秒。如果作业分布在越来越多的线程中,这个问题会变得更加严重,这是我的问题所在。

对于int[65]String[65]等不同类型的数组,也会出现此问题。 大字符串不会出现此问题:String test = "many characters";,但在将其更改为String test = i + "";时会开始发生

我想知道为什么会这样,如果有可能绕过这个问题。

2 个答案:

答案 0 :(得分:88)

您正在观察由Java VM的JIT编译器执行的优化导致的行为。此行为可通过最多64个元素的标量数组触发,并且不会被大于64的数组触发。

在详细介绍之前,让我们仔细看看循环体:

double[] test = new double[64];

正文无效(可观察行为)。这意味着无论是否执行此语句,它在程序执行之外都没有区别。整个循环也是如此。所以可能会发生,代码优化器将循环转换为具有相同功能和不同时序行为的(或没有)

对于基准测试,您至少应遵循以下两条准则。如果你这样做了,差异就会明显变小。

  • 通过多次执行基准来预热JIT编译器(和优化器)。
  • 使用每个表达式的结果并在基准测试结束时打印它。

现在让我们详细介绍一下。毫不奇怪,对于不超过64个元素的标量数组,会触发优化。优化是{{​​3}}的一部分。它将小对象和小数组放入堆栈而不是在堆上分配它们 - 甚至更好地完全优化它们。您可以在Brian Goetz于2005年撰写的以下文章中找到有关它的一些信息:

可以使用命令行选项-XX:-DoEscapeAnalysis禁用优化。标量数组的魔术值64也可以在命令行中更改。如果按如下方式执行程序,则具有64和65个元素的数组之间没有区别:

java -XX:EliminateAllocationArraySizeLimit=65 Tests

话虽如此,我强烈反对使用这样的命令行选项。我怀疑它在现实应用中有很大的不同。我只会使用它,如果我绝对相信必要性 - 而不是基于某些伪基准测试的结果。

答案 1 :(得分:2)

根据对象的大小,可以有多种方式存在差异。

正如nosid所说,JITC可能(很可能是)在堆栈上分配小的“本地”对象,而“小”数组的大小截止可能是64个元素。

在堆栈上分配比在堆中分配要快得多,而且更重要的是,堆栈不需要进行垃圾回收,因此GC开销大大降低。 (对于此测试用例,GC开销可能占总执行时间的80-90%。)

此外,一旦值被堆栈分配,JITC就可以执行“死代码消除”,确定new的结果从未在任何地方使用,并且在确保没有任何副作用之后丢失,消除整个new操作,然后消除(现在为空)循环本身。

即使JITC不进行堆栈分配,对于小于特定大小的对象,完全有可能在堆中以不同的方式(例如,来自不同的“空间”)而不是更大的对象来分配。 (通常情况下,这不会产生如此戏剧性的时间差异。)