类型化数组是否有助于 JIT 更好地优化?

Do typed arrays help the JIT to optimize better?

我的问题如下:

Java 代码通常会实现通用集合,例如:

public class GenericCollection<T> {
    private Object[] data;

    public GenericCollection () {
        // Backing array is a plain object array.
        this.data = new Object[10];
    }

    @SuppressWarnings( "unchecked" )
    public T get(int index) {
        // And we just cast to appropriate type when needed.
        return (T) this.data[index];
    }
}

并像这样使用例如:

for (MyObject obj : genericCollection) {
    obj.myObjectMethod();
}

由于 genericCollection 的泛型类型被擦除,JVM 似乎没有办法知道在 'data' genericCollection 数组中真的只有 MyObject 实例,因为数组的实际类型是 Object,其中可能有一个 String,并且对其调用 'myObjectMethod' 会引发异常。

所以我假设 JVM 必须执行一些运行时检查体操以了解该 GenericCollection 实例中的真实内容。

现在看看这个实现:

public class GenericCollection<T> {
    private T[] data;

    @SuppressWarnings( "unchecked" )
    public GenericCollection ( Class<T> type ) {
        // Create a type specific array.
        this.data = (T[]) Array.newInstance( type, 10 );
    }

    public T get ( int index ) {
        // No unsafe casts needed.
        return this.data[index];
    }
}

在这种情况下,我们通过反射创建了一个类型特定的数组,因此 JVM 可以推断在给定的上下文中该数组中只能有 T 个对象,从而使不安全的转换和可能的昂贵类型检查变得多余。

我的问题是,考虑到 HotSpot 可以做的事情,它会以任何方式,在性能方面帮助实现具有 "proper" 类型特定支持数组的通用集合吗?

例如,它是否有助于 HotSpot 删除不必要的类型检查或强制转换?如果它知道后备数组是特定类型,也许可以使它更容易地内联方法?

没有。 type erasure Java 教程说明

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.

因此,经过编译后,泛型类型为Object

在这个特殊情况下不是。

通用数组 T[] 在字节码中被擦除为 Object[]Object[]的数组getter总是returnsObject,所以不需要检查数组的实际类型。因此,使用 T[] 而不是 Object[] 进行数组获取操作没有任何好处。在这两种情况下,aaload 指令后跟 checkcast,其工作方式相同。

同时,数组 setter 对于类型化数组的性能比 Object[] 更差,因为 aastore 必须检查值是否与实际的数组组件类型匹配。

也就是说,您提议的修改 get 的效果相同 ,但对 set 的效果 更差 。这可以通过以下 JMH 基准测试得到证实。

package bench;

import org.openjdk.jmh.annotations.*;

import java.lang.reflect.Array;

@State(Scope.Benchmark)
public class Generics {
    private ObjectArray<String> objectArray;
    private GenericArray<String> genericArray;
    private StringArray stringArray;
    private int index;

    @Param("100000")
    private int length;

    @Setup
    public void setup() {
        genericArray = new GenericArray<>(String.class, length);
        objectArray = new ObjectArray<>(length);
        stringArray = new StringArray(length);

        for (int i = 0; i < length; i++) {
            String s = Integer.toString(i);
            objectArray.set(i, s);
            genericArray.set(i, s);
            stringArray.set(i, s);
        }
    }

    @Benchmark
    public String getGenericArray() {
        return genericArray.get(nextIndex());
    }

    @Benchmark
    public String getObjectArray() {
        return objectArray.get(nextIndex());
    }

    @Benchmark
    public String getStringArray() {
        return stringArray.get(nextIndex());
    }

    @Benchmark
    public void setGenericArray() {
        genericArray.set(nextIndex(), "value");
    }

    @Benchmark
    public void setObjectArray() {
        objectArray.set(nextIndex(), "value");
    }

    @Benchmark
    public void setStringArray() {
        stringArray.set(nextIndex(), "value");
    }

    private int nextIndex() {
        if (++index == length) index = 0;
        return index;
    }

    static class GenericArray<T> {
        private T[] data;

        @SuppressWarnings("unchecked")
        public GenericArray(Class<T> type, int length) {
            this.data = (T[]) Array.newInstance(type, length);
        }

        public T get(int index) {
            return data[index];
        }

        public void set(int index, T value) {
            data[index] = value;
        }
    }

    static class ObjectArray<T> {
        private Object[] data;

        public ObjectArray(int length) {
            this.data = new Object[length];
        }

        @SuppressWarnings("unchecked")
        public T get(int index) {
            return (T) data[index];
        }

        public void set(int index, T value) {
            data[index] = value;
        }
    }

    static class StringArray {
        private String[] data;

        public StringArray(int length) {
            this.data = new String[length];
        }

        public String get(int index) {
            return data[index];
        }

        public void set(int index, String value) {
            data[index] = value;
        }
    }
}

结果:

Benchmark                 (length)  Mode  Cnt  Score   Error  Units
Generics.getGenericArray    100000  avgt   40  5,212 ± 0,038  ns/op  <- equal
Generics.getObjectArray     100000  avgt   40  5,224 ± 0,043  ns/op  <-
Generics.getStringArray     100000  avgt   40  4,557 ± 0,051  ns/op
Generics.setGenericArray    100000  avgt   40  3,299 ± 0,032  ns/op  <- worse
Generics.setObjectArray     100000  avgt   40  2,456 ± 0,007  ns/op  <-
Generics.setStringArray     100000  avgt   40  2,138 ± 0,008  ns/op