Rendimiento de reflexión de Java

Resuelto dmanxiii asked hace 15 años • 14 respuestas

¿La creación de un objeto mediante la reflexión en lugar de llamar al constructor de la clase produce diferencias significativas en el rendimiento?

dmanxiii avatar Jan 12 '09 21:01 dmanxiii
Aceptado

Si, absolutamente. Buscar una clase mediante reflexión es, por magnitud , más caro.

Citando la documentación de Java sobre la reflexión :

Dado que la reflexión implica tipos que se resuelven dinámicamente, no se pueden realizar determinadas optimizaciones de la máquina virtual Java. En consecuencia, las operaciones reflectantes tienen un rendimiento más lento que sus contrapartes no reflectantes y deben evitarse en secciones de código que se llaman con frecuencia en aplicaciones sensibles al rendimiento.

Aquí hay una prueba simple que hackeé en 5 minutos en mi máquina, ejecutando Sun JRE 6u10:

public class Main {

    public static void main(String[] args) throws Exception
    {
        doRegular();
        doReflection();
    }

    public static void doRegular() throws Exception
    {
        long start = System.currentTimeMillis();
        for (int i=0; i<1000000; i++)
        {
            A a = new A();
            a.doSomeThing();
        }
        System.out.println(System.currentTimeMillis() - start);
    }

    public static void doReflection() throws Exception
    {
        long start = System.currentTimeMillis();
        for (int i=0; i<1000000; i++)
        {
            A a = (A) Class.forName("misc.A").newInstance();
            a.doSomeThing();
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

Con estos resultados:

35 // no reflection
465 // using reflection

Tenga en cuenta que la búsqueda y la creación de instancias se realizan juntas y, en algunos casos, la búsqueda se puede refactorizar, pero este es solo un ejemplo básico.

Incluso si simplemente crea una instancia, aún obtendrá un impacto en el rendimiento:

30 // no reflection
47 // reflection using one lookup, only instantiating

De nuevo, YMMV.

Yuval Adam avatar Jan 12 '2009 14:01 Yuval Adam

Sí, es más lento.

Pero recuerde la maldita regla número uno: LA OPTIMIZACIÓN PREMATURA ES LA RAÍZ DE TODO MAL

(Bueno, puede estar empatado con el número 1 para SECO)

Lo juro, si alguien se acercara a mí en el trabajo y me preguntara esto, estaría muy atento a su código durante los próximos meses.

Nunca debes optimizar hasta que estés seguro de que lo necesitas; hasta entonces, simplemente escribe un código bueno y legible.

Ah, y tampoco me refiero a escribir código estúpido. Simplemente piense en la forma más limpia en la que pueda hacerlo: sin copiar y pegar, etc. (Aun así, tenga cuidado con cosas como bucles internos y use la colección que mejor se adapte a sus necesidades; ignorarlos no es programación "no optimizada"). , es una programación "mala")

Me asusta cuando escucho preguntas como esta, pero luego olvido que todos tienen que aprender todas las reglas por sí mismos antes de entenderlas realmente. Lo obtendrá después de haber pasado un mes-hombre depurando algo que alguien "optimizó".

EDITAR:

En este hilo sucedió algo interesante. Verifique la respuesta n.° 1, es un ejemplo de cuán poderoso es el compilador para optimizar cosas. La prueba es completamente inválida porque la instanciación no reflexiva se puede descartar por completo.

¿Lección? NUNCA optimice hasta que haya escrito una solución limpia y claramente codificada y haya demostrado que es demasiado lenta.

Bill K avatar Feb 13 '2009 22:02 Bill K

Es posible que descubra que la JVM está optimizando A a = new A(). Si colocas los objetos en una matriz, no funcionan tan bien. ;) Las siguientes impresiones...

new A(), 141 ns
A.class.newInstance(), 266 ns
new A(), 103 ns
A.class.newInstance(), 261 ns

public class Run {
    private static final int RUNS = 3000000;

    public static class A {
    }

    public static void main(String[] args) throws Exception {
        doRegular();
        doReflection();
        doRegular();
        doReflection();
    }

    public static void doRegular() throws Exception {
        A[] as = new A[RUNS];
        long start = System.nanoTime();
        for (int i = 0; i < RUNS; i++) {
            as[i] = new A();
        }
        System.out.printf("new A(), %,d ns%n", (System.nanoTime() - start)/RUNS);
    }

    public static void doReflection() throws Exception {
        A[] as = new A[RUNS];
        long start = System.nanoTime();
        for (int i = 0; i < RUNS; i++) {
            as[i] = A.class.newInstance();
        }
        System.out.printf("A.class.newInstance(), %,d ns%n", (System.nanoTime() - start)/RUNS);
    }
}

Esto sugiere que la diferencia es de aproximadamente 150 ns en mi máquina.

Peter Lawrey avatar Feb 13 '2009 21:02 Peter Lawrey

Si realmente se necesita algo más rápido que la reflexión, y no se trata solo de una optimización prematura, entonces la generación de código de bytes con ASM o una biblioteca de nivel superior es una opción. Generar el código de bytes la primera vez es más lento que simplemente usar la reflexión, pero una vez que se ha generado el código de bytes, es tan rápido como el código Java normal y será optimizado por el compilador JIT.

Algunos ejemplos de aplicaciones que utilizan generación de código:

  • Invocar métodos en proxies generados por CGLIB es ligeramente más rápido que los proxies dinámicos de Java , porque CGLIB genera código de bytes para sus proxies, pero los proxies dinámicos solo usan reflexión ( mediré que CGLIB era aproximadamente 10 veces más rápido en las llamadas a métodos, pero la creación de los proxies fue más lenta).

  • JSerial genera código de bytes para leer/escribir los campos de objetos serializados, en lugar de utilizar la reflexión. Hay algunos puntos de referencia en el sitio de JSerial.

  • No estoy 100% seguro (y no tengo ganas de leer la fuente ahora), pero creo que Guice genera código de bytes para realizar la inyección de dependencia. Corrígeme si estoy equivocado.

Esko Luontola avatar Feb 13 '2009 23:02 Esko Luontola