Rendimiento de java.lang.reflect.Array

Como estoy haciendo un uso intensivo del acceso reflexivo a las matrices en un proyecto, decidí comparar el rendimiento dearray[index] vsjava.lang.reflect.Array.get(array, index). Aunque anticipé que las llamadas reflexivas son un poco más lentas, me sorprendió ver que son entre 10 y 16 veces más lentas.

Así que decidí escribir un método de utilidad simple que haga casi lo mismo queArray#get pero recibe la matriz en el índice dado al convertir el objeto en lugar de usar un método nativo (como lo haceArray#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];
}

Creo que este método proporciona la misma funcionalidad queArray#get, con la notable diferencia de las excepciones lanzadas (por ejemplo, unClassCastException es arrojado en lugar de unIllegalArgumentException, si se llama al método con unObject eso no es una matriz).

Para mi sorpresa, este método de utilidad realizamucho mejor queArray#get.

Tres preguntas:

¿Otros experimentan los mismos problemas de rendimiento conArray#get, o es quizás un problema de hardware / plataforma / versión de Java (probé con Java 8 en una computadora portátil con doble núcleo de Windows 7)?¿Echo de menos algo relacionado con la funcionalidad del método?Array#get? Es decir. ¿Hay alguna funcionalidad que necesariamente debe implementarse usando una llamada nativa?¿Hay una razón específica por la queArray#get se implementó utilizando métodos nativos, cuando la misma funcionalidad podría haberse implementado en Java puro con un rendimiento mucho mayor?

Clases de prueba y resultados

Las pruebas se han realizado utilizandoCalibrar (El último Caliper de git es necesario para compilar el código). Pero para su comodidad, también incluí un método principal que realiza una prueba simplificada (debe eliminar las anotaciones de Caliper para que se compile).

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

Los resultados de Caliper se pueden veraquí.

Los resultados del método principal simplificado se ven así en mi máquina:

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

Información Adicional

Los resultados son similares cuando se ejecuta la JVM con "-server"El otroArray métodos (p. ej.Array#getInt, Array#getLength, Array#set etc.) también funcionan mucho más lentamente que los métodos de utilidad implementados de manera similarEsta pregunta está algo relacionada con:¿Cuál es el propósito de los métodos getter y setter de java.lang.reflect.Array?

Respuestas a la pregunta(1)

Su respuesta a la pregunta