21

为什么第二段代码更快?

Map<Integer, Double> map = new HashMap<Integer, Double>();
for (int i = 0; i < 50000; i++) {
    for (double j = 0.0; j < 10000; j++) {
        map.put(i, j);
    }
}

Map<Integer, Double> map=new HashMap<Integer, Double>();
for (int i = 0; i < 50000; i++) {
    for (double j = 0.0; j < 10000; j++) {            
        map.put(new Integer(i), new Double(j));
    }
}
4

3 回答 3

49

Autoboxing 使用Integer.valueOf,它在内部缓存小整数的 Integer 对象(默认为 -128 到 127,但可以使用“java.lang.Integer.IntegerCache.high”属性配置最大值 - 参见 Integer.valueOf 的源代码) ,所以它不同于new Integer直接调用。因为Integer.valueOf在调用之前会快速检查整数值的大小new Integer,所以直接调用会快一点new Integer(尽管如果你有很多小整数,它会使用更多的内存)。Java 中的分配速度非常快,而且做 GC 的时间与存活的短期对象的数量成正比(即与垃圾的数量不成正比),因此 GC 也非常快。

但是根据 JVM 版本和启用了哪些优化,存在标量替换优化,这会在分配短期对象时产生更大的性能差异(在您的示例中,无法进行优化,因为您正在存储地图中的对象,但在许多其他情况下它很有用)。

在最近的 JVM 版本中,有标量替换优化(除了在 1.6.0_18 中暂时禁用转义分析),这意味着可以优化短期对象的分配。当 JVM 中的标量替换是新的时,有人做了一个基准测试,其中有与您的代码相似的代码。结果是使用原语的代码最快,显式new Integer()调用的代码几乎和使用原语的代码一样快,而使用自动装箱的代码要慢得多。这是因为自动装箱使用Integer.valueOf以及至少在当时的标量替换优化没有考虑到这种特殊情况。我不知道从那以后优化是否得到了改进。

于 2010-02-22T00:04:30.833 回答
14

自动装箱将使用Integer.valueOfDouble.valueOf。调用这些方法有一些开销(尽管它最终会被内联)。还会Integer.valueOf检查低值以使用池化实例,这在您的代码中并不常见(尽管它可以稍微减少堆大小)。池化实例可能是一个胜利,它们可以减少堆大小、GC 时间,甚至可以提高相等性测试性能。

但是,一般来说,这是一个微优化,一般来说,你应该忽略它。

于 2010-02-21T23:31:24.103 回答
7

因为微基准测试的结果不可靠?

此外,自动装箱是使用 Integer.valueOf() 和 Double.valueOf() 完成的,而不是构造函数。

于 2010-02-21T23:30:26.533 回答