java.lang.reflect.Array

时间:2015-05-18 14:40:32

标签: java arrays performance reflection

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

所以我决定编写一个与Array#get大致相同的简单实用程序方法,但是通过强制转换对象而不是使用本机方法来接收给定索引处的数组(就像Array#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];
}

我相信这个方法提供与Array#get相同的功能,抛出异常的显着差异(例如,ClassCastException被抛出而不是IllegalArgumentException,如果有人调用Object没有数组的方法。)。

令我惊讶的是,此实用程序方法比Array#get执行

三个问题:

  1. 这里的其他人遇到与Array#get相同的性能问题,或者这可能是硬件/平台/ Java版本问题(我在双核Windows 7笔记本电脑上使用Java 8进行了测试)?
  2. 我是否遗漏了有关方法Array#get功能的一些内容?即是否必须使用本机调用实现某些功能?
  3. 是否有一个特定的原因,为什么Array#get是使用本机方法实现的,当相同的功能可以在纯Java中以更高的性能实现时?
  4. 测试类和结果

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

    识别TestClass:

    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);
            }
        }
    }
    

    可以查看Caliper结果here

    简化主要方法的结果在我的机器上看起来像这样:

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

    其他信息

1 个答案:

答案 0 :(得分:8)

是的,Array.get在OpenJDK / Oracle JDK中运行缓慢,因为它是由本机方法实现的,并未由JIT优化。

Array.get没有特殊原因,除了从最早的JDK版本(当JVM不太好而且根本没有JIT)时就已经这样了。此外,GNU Classpath还有一个java.lang.reflect.Array的纯Java compatible implementation

目前(从JDK 8u45开始),只有Array.newInstanceArray.getLength被优化(是JVM内在函数)。看起来没有人真正关心反射get / set方法的性能。但是作为@ Marco13 noticed,有一个未解决的问题JDK-8051447可以在将来提高Array.*方法的性能。