使用JIT编译器的Collections.emptyList和空ArrayList的性能

时间:2015-10-24 11:37:11

标签: java performance jit jvm-hotspot

使用Collections.emptyList()或空ArrayList之间是否存在性能差异,尤其是在使用JIT编译器时?

我可以想象 - 例如 - JIT编译器不进行内联或静态方法调用,因为执行的方法取决于类型。

修改 我知道Collections.emptyList()返回一个不可变列表,而ArrayList是可变对象。

我的意思是,如果我将一个或另一个方法作为参数传递给方法,并且方法不修改列表,那么限制了JIT编译器优化方法的可能性吗?

一个简单的例子(只是为了澄清我的意思):

int sum(List<Integer> list)
{
    int sum = 0;

    for(int i=0;i<list.size();++i)
      sum += list.get(i);

    return sum;
}

如果我只使用ArrayList调用此方法,则JIT编译器可以内联ArrayList.get()。如果我也使用Collections.empty()拨打电话,那就不可能了。

这是对的吗?

3 个答案:

答案 0 :(得分:6)

Disclamer

以下所有内容仅适用于HotSpot JVM

简短答案

  

JIT编译器不进行内联或静态方法调用,因为   执行的方法取决于类型。

这与事实相反。看到我的回答。

  

使用之间是否存在性能差异   Collections.emptyList()或空ArrayList,尤其是在使用时   JIT编译器?

在极少数情况下 - 是的。请参阅microbenchmark结果。

  

如果我只使用ArrayList调用此方法,则JIT编译器可以   内联ArrayList.get()。如果我也使用Collections.empty()进行调用   它是不可能的。这是对的吗?

简短的回答 - 这取决于。 JIT编译器非常智能,可以识别单态,双态和多态调用模式,并提供适当的实现。

答案

为了得到详细的答案,我建议您阅读以下post关于方法调度的黑魔法。用几句话说

  

C2做了一个有趣的配置文件引导优化基于   观察到的类型如果只有一个接收器类型(那个   是,呼叫站点是单态),它可以简单地检查   预测类型,并直接内联目标。同样的优化   如果观察到两种接收器类型,则可以并且将被应用(   是,呼叫站点是 bimorphic ),代价是两个分支。

让我们考虑以下JMH示例(如果您还没有了解JMH,那么我建议您阅读它here)。

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 5)
public class ExampleBench {

    @Param("10000")
    private int count;

    List<Integer>[] arrays;
    List<Integer>[] empty;
    List<Integer>[] bimorphic;
    List<Integer>[] polimorphic;

    @Setup
    public void setup(){
        Random r = new Random(0xBAD_BEEF);
        arrays = new List[count];
        empty = new List[count];
        bimorphic = new List[count];
        polimorphic = new List[count];
        for (int i = 0; i < arrays.length; i++) {
            bimorphic[i] = r.nextBoolean() ? new ArrayList<Integer>(0) : Collections.<Integer>emptyList();
            int i1 = r.nextInt(3);
            switch (i1) {
                case 0 : polimorphic[i] = new ArrayList<>(0);
                    break;
                case 1 : polimorphic[i] = new LinkedList<>();
                    break;
                case 2 : polimorphic[i] = Collections.emptyList();
                    break;
            }
            arrays[i] = new ArrayList<>(0);
            empty[i] = Collections.emptyList();
        }
    }

    @Benchmark
    public float arrayList() {
        List<Integer>[] l = arrays;
        int c = count;
        float result = 0;
        for (int i = 0; i < c; i++) {
            result += sum(l[i]);
        }
        return result;
    }

    @Benchmark
    public float emptyList() {
        List<Integer>[] l = empty;
        int c = count;
        float result = 0;
        for (int i = 0; i < c; i++) {
            result += sum(l[i]);
        }
        return result;
    }

    @Benchmark
    public float biList() {
        List<Integer>[] l = bimorphic;
        int c = count;
        float result = 0;
        for (int i = 0; i < c; i++) {
            result += sum(l[i]);
        }
        return result;
    }

    @Benchmark
    public float polyList() {
        List<Integer>[] l = polimorphic;
        int c = count;
        float result = 0;
        for (int i = 0; i < c; i++) {
            result += sum(l[i]);
        }
        return result;
    }

    int sum(List<Integer> list) {
        int sum = 0;
        for (int i = 0; i < list.size(); ++i) {
            sum += list.get(i);
        }
        return sum;
    }
}

结果是:

Benchmark               (count)  Mode  Cnt       Score       Error  Units
ExampleBench.arrayList    10000  avgt    5   22902.547 ± 27665.651  ns/op
ExampleBench.biList       10000  avgt    5   50459.552 ±   739.379  ns/op
ExampleBench.emptyList    10000  avgt    5    3745.469 ±   211.794  ns/op
ExampleBench.polyList     10000  avgt    5  164879.943 ±  5830.008  ns/op

在单态和双态调用的情况下,JIT通过具体实现替换虚拟调用。例如,对于arrayList(),我们为-XX:+PrintInlining提供了以下输出:

@ 27   edu.jvm.runtime.ExampleBench::sum (38 bytes)   inline (hot)
   @ 6   java.util.ArrayList::size (5 bytes)   accessor
    \-> TypeProfile (15648/15648 counts) = java/util/ArrayList

代表emptyList()

@ 27   edu.jvm.runtime.ExampleBench::sum (38 bytes)   inline (hot)
   @ 6   java.util.Collections$EmptyList::size (2 bytes)   inline (hot)
    \-> TypeProfile (9913/9913 counts) = java/util/Collections$EmptyList

代表biList()

@ 27   edu.jvm.runtime.ExampleBench::sum (38 bytes)   inline (hot)
   @ 6   java.util.Collections$EmptyList::size (2 bytes)   inline (hot)
   @ 6   java.util.ArrayList::size (5 bytes)   accessor
    \-> TypeProfile (2513/5120 counts) = java/util/ArrayList
    \-> TypeProfile (2607/5120 counts) = java/util/Collections$EmptyList

如果polyList() JIT没有内联任何实现并使用真正的虚拟调用。

在这些方法中使用内联函数有什么好处?让我们看看编译器为arrayList()生成的代码:

0x00007ff9e51bce50: cmp $0xf80036dc,%r10d     ;instance of 'java/util/ArrayList'
0x00007ff9e51bce57: jne L0000                 ;if false go to L0000 (invokeinterface size)
0x00007ff9e51bce59: mov 0x10(%rdx),%ebp       ;*getfield size optimization java.util.ArrayList::size@1 

.....

0x00007ff9e51bce6d: retq
             L0000: mov $0xffffffde,%esi      ; true virtual call starts here
0x00007ff9e51bce73: mov %rdx,(%rsp)
0x00007ff9e51bce77: callq 0x00007ff9e50051a0  ; OopMap{[0]=Oop off=92}
                                              ;*invokeinterface size
                                              ; - edu.jvm.runtime.ExampleBench::sum@6 (line 119)
                                              ;   {runtime_call}

正如您所见,JIT取代了getfield的虚拟来电。

答案 1 :(得分:1)

Collections.emptyList()始终返回相同的,不可变的空列表对象(单例)。另一方面,创建ArrayList实际上会创建一个新对象,分配内存,并且该对象必须稍后进行GC。

应该没有显着差异,但Collections.emptyList()的工作量较少。这些操作在功能上并不相同。一个允许获取一个不可变的空列表,而另一个允许创建一个新的可变列表。根据您需要的功能选择一个或另一个。不是出于性能原因。

答案 2 :(得分:0)

Collections.emptyList()和空new ArrayList<>()工作略有不同。集合返回的列表不仅是空的,它是不可变的,因此非常可以将列表存储为单例并在每次调用emptyList()时返回(由于类型擦除,这对于任何类型都是可能的限定符)。

所以,答案取决于你要对空列表做什么。如果您要将某些代码中的空列表作为最终值返回,Collections.emptyList()肯定会更好(它甚至不会创建新对象)。如果您要设置空列表以进行进一步修改,Collections.emptyList()是完全不可接受的

相关问题