使用JIT编译器的Collections.emptyList和空ArrayList的性能 [英] Performance of Collections.emptyList and empty ArrayList with JIT compiler

查看:135
本文介绍了使用JIT编译器的Collections.emptyList和空ArrayList的性能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Is there a performance difference between using Collections.emptyList() or an empty ArrayList, especially when using a JIT compiler?

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

I could imagine that - for example - the JIT compiler doesn't do inlining or static method calls because the executed method depends on the type.

编辑
我知道 Collections.emptyList()返回不可变列表 ArrayList 是可变对象。

Edit I know that Collections.emptyList() returns an immutable list while ArrayList is mutable object.

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

What I mean is that if I pass one or the other to a method as a parameter and the method doesn't modify the list does that restrict the possibilities for the JIT compiler to optimize the method?

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

A simple example (just to clarify what I mean):

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()打电话,那就不可能。

If I only called this method with ArrayList the JIT compiler could inline ArrayList.get(). If I also made calls with Collections.empty() it wouldn't be possible.

这是正确的吗?

推荐答案

Disclamer



以下所有内容仅适用于< a href =https://en.wikipedia.org/wiki/HotSpot =nofollow> HotSpot JVM 。


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

the JIT compiler doesn't do inlining or static method calls because the executed method depends on the type.

这与true相反。请参阅我的答案。

This is opposite of true. See my answer.


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

Is there a performance difference between using Collections.emptyList() or an empty ArrayList, especially when using a JIT compiler?

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

In rare cases - yes. See microbenchmark results.


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

If I only called this method with ArrayList the JIT compiler could inline ArrayList.get(). If I also made calls with Collections.empty() it wouldn't be possible. Is that correct?

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

The short answer - it depends. JIT compiler is smart enough to recognize monomorphic, bimorphic and polimorphic call patterns and provide appropriate implementations.

为了得到详细的答案,我建议您阅读以下帖子关于方法调度的黑魔法。简而言之

In order to get a detailed answer I would recommend to read the following post about black magic of method dispatching. In few words


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

C2 does an interesting profile-guided optimization based on the observed type profile. If there is only a single receiver type (that is, the call site is monomorphic), it can simply check for the predicted type, and inline the target directly. The same optimization can and will be applied if there are two receiver types observed (that is, the call site is bimorphic), at the cost of two branches.

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

Let's consider the following JMH example(if you haven’t learned about JMH then I suggest to read about it 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

In case of monomorphic and bimorphic calls JIT replaces virtual call by concrete implementations. For example in case of arrayList() we have the following output for -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没有内联任何实现并使用真正的虚拟调用。

In case of polyList() JIT does not inline any implementation and uses true virtual call.

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

What is the advantages of using inline functions in these methods? Let's look at compiler-generated code for 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替换虚拟呼叫

As you can see JIT replaces virtual call by getfield.

这篇关于使用JIT编译器的Collections.emptyList和空ArrayList的性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆