java类型化数组有助于jit更好地优化吗?

w51jfk4q  于 2021-07-13  发布在  Java
关注(0)|答案(2)|浏览(376)

我的问题如下:
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似乎无法知道真正在genericcollection的“data”数组中只有myobject示例,因为数组的实际类型是object,所以其中可能有一个字符串,对其调用“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可以做的事情,在性能方面,使用“适当的”特定于类型的支持数组实现泛型集合是否会有所帮助?
例如,它是否有助于消除不必要的类型检查或强制转换?如果它知道backing数组是特定类型的,那么是否可能使它更容易内联方法?

72qzrwbm

72qzrwbm1#

不是在这个特殊的情况下。
泛型数组 T[] 删除为 Object[] 在字节码中。的数组getter Object[] 总是回来 Object ,因此不需要检查实际的数组类型。因此没有好处 T[] 而不是 Object[] 用于数组获取操作。在这两种情况下都有 aaload 指令后接 checkcast ,也是这样。
同时,对于类型化数组,数组设置器的性能比 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
j8ag8udp

j8ag8udp2#

不,类型擦除java教程解释了
泛型被引入到java语言中,以在编译时提供更严格的类型检查,并支持泛型编程。为了实现泛型,java编译器将类型擦除应用于:
如果泛型类型中的类型参数是无界的,则用它们的边界或对象替换所有类型参数。因此,生成的字节码只包含普通类、接口和方法。
必要时插入类型强制转换以保持类型安全。
生成桥接方法以在扩展泛型类型中保留多态性。
因此,编译之后,泛型类型是 Object .

相关问题