java.lang.reflect.Array 的性能

2022-09-03 07:46:27

由于我在项目中大量使用对数组的反射访问,因此我决定比较 与 的性能。虽然我预料到,反射呼叫的速度要慢得多,但我惊讶地发现它们的速度要慢10-16倍。array[index]java.lang.reflect.Array.get(array, index)

所以我决定编写一个简单的实用程序方法,该方法的功能与给定索引处的数组大致相同,但通过强制转换对象而不是使用本机方法(就像这样):Array#getArray#get

public static Object get(Object array, int index){
    Class<?> c = array.getClass();
    if (int[].class == c) {
        return ((int[])array)[index];
    } else if (float[].class == c) {
        return ((float[])array)[index];
    } else if (boolean[].class == c) {
        return ((boolean[])array)[index];
    } else if (char[].class == c) {
        return ((char[])array)[index];
    } else if (double[].class == c) {
        return ((double[])array)[index];
    } else if (long[].class == c) {
        return ((long[])array)[index];
    } else if (short[].class == c) {
        return ((short[])array)[index];
    } else if (byte[].class == c) {
        return ((byte[])array)[index];
    }
    return ((Object[])array)[index];
}

我相信此方法提供的功能与 相同,但抛出异常的显着差异(例如,如果使用没有数组的方法调用方法,则抛出 a 而不是 a)。Array#getClassCastExceptionIllegalArgumentExceptionObject

令我惊讶的是,此实用程序方法的性能比 .Array#get

三个问题:

  1. 这里的其他人是否遇到与 相同的性能问题,或者这可能是硬件/平台/Java版本问题(我在双核Windows 7笔记本电脑上使用Java 8进行了测试)?Array#get
  2. 我是否错过了有关该方法功能的内容?也就是说,是否有一些功能必须使用本机调用来实现?Array#get
  3. 有没有一个具体的原因,为什么使用本机方法实现,而相同的功能可以在纯Java中实现,并且性能要高得多?Array#get

测试类和结果

测试是使用 Caliper(编译代码所需的 git 的最新 Caliper)完成的。但为了您的方便,我还包含了一个执行简化测试的主要方法(您需要删除 Caliper 注释才能进行编译)。

测试类:

import java.lang.reflect.Array;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;

public class ArrayAtBenchmark {

    public static final class ArrayUtil {
        public static Object get(Object array, int index){
            Class<?> c = array.getClass();
            if (int[].class == c) {
                return ((int[])array)[index];
            } else if (float[].class == c) {
                return ((float[])array)[index];
            } else if (boolean[].class == c) {
                return ((boolean[])array)[index];
            } else if (char[].class == c) {
                return ((char[])array)[index];
            } else if (double[].class == c) {
                return ((double[])array)[index];
            } else if (long[].class == c) {
                return ((long[])array)[index];
            } else if (short[].class == c) {
                return ((short[])array)[index];
            } else if (byte[].class == c) {
                return ((byte[])array)[index];
            }
            return ((Object[])array)[index];
        }
    }

    private static final int ELEMENT_SIZE = 100;
    private Object[] objectArray;

    @BeforeExperiment
    public void setup(){
        objectArray = new Object[ELEMENT_SIZE];
        for (int i = 0; i < objectArray.length; i++) {
            objectArray[i] = new Object();
        }
    }

    @Benchmark
    public int ObjectArray_at(int reps){
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            for (int j = 0; j < ELEMENT_SIZE; j++) {
                dummy |= objectArray[j].hashCode();
            }
        }
        return dummy;
    }

    @Benchmark
    public int ObjectArray_Array_get(int reps){
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            for (int j = 0; j < ELEMENT_SIZE; j++) {
                dummy |= Array.get(objectArray, j).hashCode();
            }
        }
        return dummy;
    }

    @Benchmark
    public int ObjectArray_ArrayUtil_get(int reps){
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            for (int j = 0; j < ELEMENT_SIZE; j++) {
                dummy |= ArrayUtil.get(objectArray, j).hashCode();
            }
        }
        return dummy;
    }

    // test method to use without Cailper
    public static void main(String[] args) {
        ArrayAtBenchmark benchmark = new ArrayAtBenchmark();
        benchmark.setup();

        int warmup = 100000;
        // warm up 
        benchmark.ObjectArray_at(warmup);
        benchmark.ObjectArray_Array_get(warmup);
        benchmark.ObjectArray_ArrayUtil_get(warmup);

        int reps = 100000;

        long start = System.nanoTime();
        int temp = benchmark.ObjectArray_at(reps);
        long end = System.nanoTime();
        long time = (end-start)/reps;
        System.out.println("time for ObjectArray_at: " + time + " NS");

        start = System.nanoTime();
        temp |= benchmark.ObjectArray_Array_get(reps);
        end = System.nanoTime();
        time = (end-start)/reps;
        System.out.println("time for ObjectArray_Array_get: " + time + " NS");

        start = System.nanoTime();
        temp |= benchmark.ObjectArray_ArrayUtil_get(reps);
        end = System.nanoTime();
        time = (end-start)/reps;
        System.out.println("time for ObjectArray_ArrayUtil_get: " + time + " NS");
        if (temp == 0) {
            // sanity check to prevent JIT to optimize the test methods away
            System.out.println("result:" + result);
        }
    }
}

卡钳结果可以在这里查看。

简化的 main 方法的结果在我的机器上如下所示:

time for ObjectArray_at: 620 NS
time for ObjectArray_Array_get: 10525 NS
time for ObjectArray_ArrayUtil_get: 1287 NS

附加信息


答案 1

是的,在OpenJDK / Oracle JDK中很慢,因为它是由本机方法实现的,而不是由JIT优化的。Array.get

没有特别的理由是原生的,除了从JDK的最早版本(当时JVM不是那么好,根本没有JIT)开始就一直如此。此外,还有一个来自 GNU Classpath 的纯 Java 兼容实现Array.getjava.lang.reflect.Array

目前(从JDK 8u45开始),只有Array.newInstanceArray.getLength进行了优化(是JVM内部函数)。看起来没有人真正关心反射式获取/设置方法的性能。但正如@Marco13注意到JDK-8051447存在一个悬而未决的问题,用于在将来的某个时候提高方法的性能。Array.*


答案 2