¿Crear un objeto usando la reflexión en lugar de llamar al constructor de la clase da como resultado alguna diferencia de rendimiento significativa?
java
performance
optimization
reflection
dmanxiii
fuente
fuente
Respuestas:
Si, absolutamente. Buscar una clase a través de la reflexión es, por magnitud , más caro.
Citando la documentación de Java sobre la reflexión :
Aquí hay una prueba simple que pirateé 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 solo crea una instancia, aún obtiene un impacto en el rendimiento:
30 // no reflection 47 // reflection using one lookup, only instantiating
Nuevamente, YMMV.
fuente
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 # 1 para DRY)
Lo juro, si alguien se me acercara en el trabajo y me preguntara esto, estaría muy atento a su código durante los próximos meses.
Nunca debe optimizar hasta que esté seguro de que lo necesita, hasta entonces, simplemente escriba un código bueno y legible.
Ah, y tampoco me refiero a escribir código estúpido. Solo piense en la forma más limpia en que puede hacerlo: sin copiar y pegar, etc. (aún desconfíe de cosas como los bucles internos y use la colección que mejor se adapte a sus necesidades: ignorar estos no es una programación "no optimizada" , es una "mala" programación)
Me asusta cuando escucho preguntas como esta, pero luego olvido que todos tienen que aprender todas las reglas por sí mismos antes de entenderlas. Lo obtendrá después de haber pasado un mes-hombre depurando algo que alguien "Optimizó".
EDITAR:
Algo interesante sucedió en este hilo. Compruebe la respuesta n. ° 1, es un ejemplo de lo poderoso que es el compilador para optimizar las cosas. La prueba es completamente inválida porque la instanciación no reflectante puede descartarse por completo.
¿Lección? No optimice NUNCA hasta que haya escrito una solución limpia y codificada y haya demostrado que es demasiado lenta.
fuente
Puede encontrar que A a = new A () está siendo optimizado por la JVM. Si coloca 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.
fuente
Class.getDeclaredMethod
) y luego llamoMethod.invoke
varias veces? ¿Estoy usando la reflexión una o tantas veces como la invoco? Pregunta de seguimiento, ¿quéMethod
pasa si en lugar de hacerlo es unConstructor
y lo hagoConstructor.newInstance
varias veces?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 por 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:
La invocación de métodos de proxies generados por CGLIB es ligeramente más rápido que el de Java proxies dinámicos , porque CGLIB genera el código de bytes de sus aliados, pero proxies dinámicos utilice sólo la reflexión ( Medí CGLIB en alrededor de 10 veces más rápido en las llamadas de método, pero la creación de las proxies era más lento).
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 un código de bytes para hacer una inyección de dependencia. Corrígeme si me equivoco.
fuente
"Significativo" depende completamente del contexto.
Si está utilizando la reflexión para crear un único objeto de controlador basado en algún archivo de configuración y luego pasa el resto de su tiempo ejecutando consultas de base de datos, entonces es insignificante. Si está creando una gran cantidad de objetos mediante la reflexión en un bucle cerrado, entonces sí, es significativo.
En general, la flexibilidad del diseño (¡donde sea necesario!) Debería impulsar el uso de la reflexión, no el rendimiento. Sin embargo, para determinar si el rendimiento es un problema, debe crear un perfil en lugar de obtener respuestas arbitrarias de un foro de discusión.
fuente
Hay algo de sobrecarga con la reflexión, pero es mucho más pequeña en las máquinas virtuales modernas de lo que solía ser.
Si está utilizando la reflexión para crear cada objeto simple en su programa, entonces algo está mal. Usarlo ocasionalmente, cuando tenga una buena razón, no debería ser un problema en absoluto.
fuente
Sí, hay un impacto en el rendimiento al usar Reflection, pero una posible solución para la optimización es almacenar en caché el método:
Method md = null; // Call while looking up the method at each iteration. millis = System.currentTimeMillis( ); for (idx = 0; idx < CALL_AMOUNT; idx++) { md = ri.getClass( ).getMethod("getValue", null); md.invoke(ri, null); } System.out.println("Calling method " + CALL_AMOUNT+ " times reflexively with lookup took " + (System.currentTimeMillis( ) - millis) + " millis"); // Call using a cache of the method. md = ri.getClass( ).getMethod("getValue", null); millis = System.currentTimeMillis( ); for (idx = 0; idx < CALL_AMOUNT; idx++) { md.invoke(ri, null); } System.out.println("Calling method " + CALL_AMOUNT + " times reflexively with cache took " + (System.currentTimeMillis( ) - millis) + " millis");
resultará en:
[java] El método de llamada 1000000 veces de forma reflexiva con búsqueda tomó 5618 milis
[java] El método de llamada 1000000 veces de forma refleja con caché tomó 270 milis
fuente
Curiosamente, configurar setAccessible (verdadero), que omite los controles de seguridad, tiene una reducción del 20% en el costo.
Sin setAccessible (verdadero)
new A(), 70 ns A.class.newInstance(), 214 ns new A(), 84 ns A.class.newInstance(), 229 ns
Con setAccessible (verdadero)
new A(), 69 ns A.class.newInstance(), 159 ns new A(), 85 ns A.class.newInstance(), 171 ns
fuente
1000000
invocaciones?setAccessible()
puede tener mucha más diferencia en general, especialmente para métodos con múltiples argumentos, por lo que siempre se debe llamar.La reflexión es lenta, aunque la asignación de objetos no es tan desesperada como otros aspectos de la reflexión. Lograr un rendimiento equivalente con la creación de instancias basada en reflexión requiere que escriba su código para que el jit pueda decir qué clase se está instanciando. Si no se puede determinar la identidad de la clase, el código de asignación no se puede insertar. Peor aún, el análisis de escape falla y el objeto no se puede asignar en la pila. Si tiene suerte, la generación de perfiles en tiempo de ejecución de la JVM puede acudir al rescate si este código se pone caliente, y puede determinar dinámicamente qué clase predomina y puede optimizar para esa.
Tenga en cuenta que los microbenchmarks en este hilo tienen fallas profundas, así que tómelos con un grano de sal. El menos defectuoso, con mucho, es el de Peter Lawrey: hace carreras de calentamiento para poner los métodos en funcionamiento, y (conscientemente) anula el análisis de escape para garantizar que las asignaciones estén realmente ocurriendo. Sin embargo, incluso ese tiene sus problemas: por ejemplo, se puede esperar que la tremenda cantidad de almacenes de arreglos derrote los cachés y los búferes de almacenamiento, por lo que esto terminará siendo principalmente un punto de referencia de memoria si sus asignaciones son muy rápidas. (Sin embargo, felicitaciones a Peter por haber llegado a la conclusión correcta: que la diferencia es "150ns" en lugar de "2.5x". Sospecho que hace este tipo de cosas para ganarse la vida).
fuente
Sí, es significativamente más lento. Estábamos ejecutando un código que hacía eso, y aunque no tengo las métricas disponibles en este momento, el resultado final fue que tuvimos que refactorizar ese código para no usar la reflexión. Si sabe cuál es la clase, simplemente llame al constructor directamente.
fuente
En doReflection () está la sobrecarga debido a Class.forName ("misc.A") (que requeriría una búsqueda de clase, posiblemente escaneando la ruta de clase en el sistema de archivos), en lugar de la newInstance () llamada en la clase. Me pregunto cómo se verían las estadísticas si Class.forName ("misc.A") se realiza solo una vez fuera del bucle for, realmente no tiene que hacerse para cada invocación del bucle.
fuente
Sí, siempre será más lento crear un objeto por reflexión porque la JVM no puede optimizar el código en tiempo de compilación. Consulte los tutoriales de Sun / Java Reflection para obtener más detalles.
Vea esta sencilla prueba:
public class TestSpeed { public static void main(String[] args) { long startTime = System.nanoTime(); Object instance = new TestSpeed(); long endTime = System.nanoTime(); System.out.println(endTime - startTime + "ns"); startTime = System.nanoTime(); try { Object reflectionInstance = Class.forName("TestSpeed").newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } endTime = System.nanoTime(); System.out.println(endTime - startTime + "ns"); } }
fuente
Class.forName()
) de la instanciación (nuevaInstancia ()), porque varían significativamente en sus características de rendimiento y ocasionalmente puede evitar la búsqueda repetida en un sistema bien diseñado.A menudo, puede usar Apache commons BeanUtils o PropertyUtils que hacen introspección (básicamente almacenan en caché los metadatos sobre las clases para que no siempre necesiten usar la reflexión).
fuente
Creo que depende de qué tan ligero / pesado sea el método objetivo. si el método objetivo es muy ligero (por ejemplo, captador / definidor), podría ser 1 ~ 3 veces más lento. si el método de destino tarda aproximadamente 1 milisegundo o más, el rendimiento será muy similar. aquí está la prueba que hice con Java 8 y reflectasm :
public class ReflectionTest extends TestCase { @Test public void test_perf() { Profiler.run(3, 100000, 3, "m_01 by refelct", () -> Reflection.on(X.class)._new().invoke("m_01")).printResult(); Profiler.run(3, 100000, 3, "m_01 direct call", () -> new X().m_01()).printResult(); Profiler.run(3, 100000, 3, "m_02 by refelct", () -> Reflection.on(X.class)._new().invoke("m_02")).printResult(); Profiler.run(3, 100000, 3, "m_02 direct call", () -> new X().m_02()).printResult(); Profiler.run(3, 100000, 3, "m_11 by refelct", () -> Reflection.on(X.class)._new().invoke("m_11")).printResult(); Profiler.run(3, 100000, 3, "m_11 direct call", () -> X.m_11()).printResult(); Profiler.run(3, 100000, 3, "m_12 by refelct", () -> Reflection.on(X.class)._new().invoke("m_12")).printResult(); Profiler.run(3, 100000, 3, "m_12 direct call", () -> X.m_12()).printResult(); } public static class X { public long m_01() { return m_11(); } public long m_02() { return m_12(); } public static long m_11() { long sum = IntStream.range(0, 10).sum(); assertEquals(45, sum); return sum; } public static long m_12() { long sum = IntStream.range(0, 10000).sum(); assertEquals(49995000, sum); return sum; } } }
El código de prueba completo está disponible en GitHub: ReflectionTest.java
fuente