sistemas - ¿Alguna forma de optimizar aún más la invocación del método reflectivo de Java?
sistema de recomendacion tesis (5)
La Optimización prematura generalmente es mala. No importa qué, su rendimiento seguirá siendo muchas veces el de un lenguaje dinámico, así que realmente no me preocuparía más allá de documentar el hecho de que utiliza la reflexión y, por lo tanto, podría ser subóptimo.
Además, existe la posibilidad de que, ahora o en el futuro, Java optimice el bytecode para que la llamada no cueste nada más que una llamada a un método cuando se utiliza en un bucle. Su "Optimización" en realidad puede obstaculizar la capacidad de los compiladores para hacer algo así. (Sé que estoy siendo vago, pero esto ha sucedido, MUCHO).
Me pregunto si hay optimizaciones adicionales que pueda implementar para mejorar la velocidad de las invocaciones reflexivas en Java. No es que el rendimiento sea prohibitivo, pero me dan escalofríos cuando pienso que algún fragmento de código en una biblioteca que estoy escribiendo se implementa en un círculo cerrado en alguna parte.
Considere un método de utilidad para invocar reflexivamente:
public static Object invoke(Object targetObject, String methodName, Object[] arguments, Class<?>[] signature)
La operación básica es
return method.invoke(targetObject, arguments);
Como optimización del rendimiento, guardo en la memoria caché el método utilizando un hash de la clase, el nombre del método y la firma del objeto de destino (cuyo código podría usar alguna mejora) pero, aparte de eso, ¿hay algo más que pueda hacer? He escuchado referencias a algunas implementaciones tempranas de InvokeDynamic que suenan prometedoras, pero supuse que probablemente todavía no eran aplicables, y descarté mi propia manipulación de código de bytes porque me gustaría mantener la utilidad simple (pero rápida).
Aclamaciones.
Los comentarios a continuación se relacionan con la implementación de Sun, en particular OpenJDK 6. Su rendimiento puede variar con otras implementaciones de la plataforma Java.
java.lang.Class
hace algo de almacenamiento en caché, por lo que implementar tu propio caché puede no mejorar mucho las cosas. Haga pruebas de tiempo con y sin almacenamiento en caché manual.
El mecanismo de invocación real también está optimizado. Las primeras 15 ejecuciones (por defecto) de su método reflejado se llaman usando JNI; después de eso, se genera bytecode y llamar a ese método reflejado funcionaría de manera idéntica a llamar a ese método directamente en el código de Java.
Definitivamente querrá reflejar el objeto de método solo una vez (probablemente como estático privado), y usarlo para la invocación en lugar de reflejarlo cada vez. No te molestes con un mapa de caché a menos que no sepas el nombre en tiempo de compilación.
Si es sensato en su contexto, es posible que desee conservar y reutilizar el argumento Array Object (no olvide anular los elementos de la matriz en la salida del método para evitar la inhibición temporal de GC).
Si siempre se invoca con los mismos parms (muy improbable), puede colgarse de los parms y (la matriz de argumentos con sus valores) reutilizarlos.
No intentaría nada más allá de eso por las razones ya dadas por las otras respuestas.
Realicé algunas pruebas en torno a la respuesta de Chris Jester-Young y utilizando las opciones detalladas , definitivamente observé que el compilador tomaba alguna acción alrededor de la 15ta invocación. Es difícil decir si hay mucho diferencial de rendimiento sin una prueba más sofisticada, pero es persuasivo. Aquí está el resultado:
Test# 0
Test# 1
Test# 2
Test# 3
Test# 4
Test# 5
Test# 6
Test# 7
Test# 8
Test# 9
Test# 10
Test# 11
Test# 12
Test# 13
Test# 14
[Loaded sun.reflect.ClassFileConstants from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.AccessorGenerator from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.ByteVectorFactory from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.ByteVector from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.ByteVectorImpl from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.ClassFileAssembler from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.UTF8 from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded java.lang.Void from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.Label from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.Label$PatchInfo from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded java.util.AbstractList$Itr from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator$1 from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.ClassDefiner from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.ClassDefiner$1 from C:/jdk1.5.0_06/jre/lib/rt.jar]
[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
Test# 15
Test# 16
Test# 17
Supongo que el negocio de InvokeDynamic no está atrayendo a demasiados desarrolladores sobre la base de la aceleración / eliminación de la reflexión.
Gracias Chris.
Si el costo de la invocación es menos del 10% del costo de lo que sucede en el método, no vale la pena preocuparse.
Puede determinarlo ejecutándolo 10 ^ 6 veces en un bucle, con y sin las agallas de la rutina. Tómelo con un cronómetro, de modo que los segundos se traduzcan a microsegundos.