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

标签 java generics optimization jvm jvm-hotspot

我的问题如下:

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 的“数据”数组中真的只有 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 可以做的事情,它是否会以任何方式在性能方面帮助实现具有“适当”类型特定支持数组的通用集合?

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

最佳答案

不是在这种特殊情况下。

通用数组 T[] 在字节码中被删除为 Object[]Object[] 的数组 getter 始终返回 Object,因此不需要检查数组的实际类型。因此,使用 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

关于java - 类型化数组是否有助于 JIT 更好地优化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36253866/

相关文章:

java - SpringMvc java.lang.NullPointerException - 配置是否正确

python - 算术表达式的优化 - 这种技术叫什么?

c++ - 优化非成本变量访问

c# - 创建不能接收空参数的方法或列表

java - "&"或 ",": What is the difference between A<T extends I1 & I2> and A<T extends I1 , I2>

java - 返回值的钻石运算符

c# - 检测 XML 的更好方法?

java - 如何在 Spring Boot 中读取 XML 属性文件中的属性

java - lock 是否锁定整个对象?

java - 处理列表中对象的属性?