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

标签 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() 进行调用,那将是不可能的。

对吗?

最佳答案

免责声明

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

简短回答

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

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

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

在极少数情况下 - 是的。查看微基准测试结果。

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 编译器足够智能,可以识别单态、双态和多态调用模式并提供适当的实现。

回答

为了获得详细的答案,我建议阅读以下内容 post关于方法调度的黑魔法。简而言之

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,那么我建议阅读它 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 代替了虚拟调用。

关于java - 使用 JIT 编译器的 Collections.emptyList 和空 ArrayList 的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33317720/

相关文章:

java,无法在 vector 中添加元素(大小0)

java - spring-aspects jar 在 AOP 中的作用?

java - 如果天数差为 x 天,则 Spring MongoDB 查询文档

postgresql - 索引组织表能否为更快的 SELECT COUNT(*) FROM 表铺平道路

javax.swing.JPanel - 在事件执行期间重新验证

PHP 脚本加载大约需要 15 秒

javascript - 网页性能 IE 只有 JS/CSS 文件

webstorm - Tailwind CSS 没有使用 'jit' 即时模式自动完成

c++ - "Live"代码和使用 C++ 和 LLVM JIT 的快速原型(prototype)制作?

ios - 系统.ExecutionEngineException : Attempting to JIT compile method only in Debug Mode on device (MonoTouch)