ejemplo - Rendimiento de reflexión de Java
text box java (14)
"Significativo" es completamente dependiente del contexto.
Si está utilizando el reflejo para crear un único objeto de controlador basado en algún archivo de configuración y luego pasa el resto del tiempo ejecutando consultas en la base de datos, entonces es insignificante. Si está creando grandes cantidades de objetos a través de la reflexión en un círculo cerrado, entonces sí, es significativo.
En general, la flexibilidad de diseño (¡cuando sea necesario!) Debería impulsar su uso de la reflexión, no del 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 debate.
¿La creación de un objeto utilizando la reflexión en lugar de llamar al constructor de la clase da como resultado diferencias de rendimiento significativas?
A menudo puede usar los recursos comunes de Apache BeanUtils o PropertyUtils que introspección (básicamente almacenan en caché los metadatos sobre las clases para que no siempre necesiten usar el reflejo).
Creo que depende de cuán ligero / pesado sea el método objetivo. si el método objetivo es muy ligero (por ejemplo, getter / setter), podría ser 1 ~ 3 veces más lento. si el método objetivo tarda aproximadamente 1 milisegundo o más, entonces el rendimiento será muy cercano. 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
Curiosamente, la configuración de setAccessible (verdadero), que omite las comprobaciones 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
En el doReflection () es la sobrecarga debido a Class.forName ("misc.A") (que requeriría una búsqueda de clase, potencialmente escaneando la ruta de clase en el sistema fils), en lugar de la nuevaInstancia () invocada en la clase. Me pregunto cómo serían las estadísticas si Class.forName ("misc.A") se realiza solo una vez fuera del ciclo for, en realidad no tiene que hacerse para cada invocación del ciclo.
Hay algunos gastos generales con reflejo, pero es mucho más pequeño en 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 tienes una buena razón, no debería ser un problema en absoluto.
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 la reflexión requiere que escribas tu código para que el jit pueda decir qué clase se está instanciando. Si no se puede determinar la identidad de la clase, entonces el código de asignación no puede estar en línea. Peor aún, el análisis de escape falla, y el objeto no puede ser asignado a la pila. Si tiene suerte, los perfiles en tiempo de ejecución de la JVM pueden llegar al rescate si este código se calienta, y pueden determinar dinámicamente qué clase predomina y puede optimizar para esa.
Tenga en cuenta que las microbenchas en este hilo son muy defectuosas, así que tómelas con un grano de sal. El menos defectuoso hasta ahora es el de Peter Lawrey: realiza corridas de calentamiento para obtener los métodos jitted, y (conscientemente) derrota 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 gran cantidad de tiendas de arreglos derroten a las memorias caché y los almacenamientos intermedios de las tiendas, por lo que esto terminará siendo principalmente un punto de referencia de memoria si sus asignaciones son muy rápidas. (Felicitaciones a Peter por haber llegado a la conclusión correcta: la diferencia es "150ns" en lugar de "2.5x". Sospecho que hace este tipo de cosas para ganarse la vida).
Puede encontrar que A a = nuevo A () está siendo optimizado por la JVM. Si pones 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.
Sí, es más lento.
Pero recuerda la maldita regla número 1: LA OPTIMIZACIÓN PREMATURA ES LA RAÍZ DE TODOS LOS MALES
(Bueno, puede estar ligado con # 1 para SECO)
Te lo juro, si alguien se me acercara en el trabajo y me preguntara esto, estaría muy pendiente de su código durante los próximos meses.
Nunca debe optimizar hasta que esté seguro de que lo necesita, hasta entonces, solo escriba un código bueno y legible.
Ah, y no me refiero a escribir un código estúpido tampoco. Solo piense en la forma más limpia posible de hacerlo: no copie y pegue, etc. (Tenga cuidado con cosas como bucles internos y use la colección que mejor se adapta a sus necesidades). Ignorar estos no es una programación "no optimizada". , es una programación "mala")
Me asusta cuando escucho preguntas como esta, pero luego me olvido de que todos tienen que aprender todas las reglas antes de que realmente lo entiendan. Lo obtendrás después de que hayas gastado un mes-mes en depurar algo que alguien "Optimizó".
EDITAR:
Algo interesante sucedió en este hilo. Verifique la respuesta n. ° 1, es un ejemplo de cuán poderoso es el compilador para optimizar las cosas. La prueba es completamente inválida porque la instanciación no reflexiva se puede excluir por completo.
¿Lección? Nunca optimices NUNCA hasta que hayas escrito una solución limpia, claramente codificada y demuestre que es demasiado lenta.
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 el reflejo. Si sabes cuál es la clase, simplemente llama al constructor directamente.
Sí, se produce 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 reflexivamente con la búsqueda tomó 5618 millis
[java] El método de llamada 1000000 veces reflexivamente con caché tomó 270 millis
Sí, siempre será más lento crear un objeto por reflexión porque la JVM no puede optimizar el código en el tiempo de compilación. Vea los tutoriales de Reflexión de Sun / Java para más detalles.
Vea esta simple 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");
}
}
Si realmente se necesita algo más rápido que la reflexión, y no solo se trata de una optimización prematura, la opción de generación de códigos de byte con ASM o de nivel superior es una opción. Generar el bytecode la primera vez es más lento que usar solo el reflejo, pero una vez que se ha generado el bytecode, es tan rápido como el código Java normal y será optimizado por el compilador JIT.
Algunos ejemplos de aplicaciones que usan generación de código:
La invocación de métodos en proxies generados por CGLIB es ligeramente más rápida que los proxies dinámicos de Java, porque CGLIB genera bytecode para sus proxies, pero los proxies dinámicos usan solo la reflexión (medí que CGLIB era aproximadamente 10 veces más rápido en las llamadas a métodos, pero la creación de los proxies era más lenta).
JSerial genera bytecode para leer / escribir los campos de objetos serializados, en lugar de usar el reflejo. 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 bytecode para hacer la inyección de dependencia. Corrígeme si estoy equivocado.
Si, absolutamente. Buscar una clase a través de la reflexión es, por magnitud , más costoso.
Citando la documentación de Java en la reflexión :
Debido a que la reflexión involucra tipos que se resuelven dinámicamente, ciertas optimizaciones de máquinas virtuales Java no se pueden realizar. En consecuencia, las operaciones reflexivas tienen un rendimiento más lento que sus contrapartes no reflectantes, y deben evitarse en las secciones de código que se denominan con frecuencia en las aplicaciones sensibles al rendimiento.
Aquí hay una prueba simple que hice 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 esto es solo un ejemplo básico.
Incluso si solo hace una instancia, todavía obtiene un golpe de rendimiento:
30 // no reflection
47 // reflection using one lookup, only instantiating
Nuevamente, YMMV.