2009-01-05 6 views
18

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.¿Alguna manera de optimizar aún más la invocación del método reflectivo de Java?

Considere un método de utilidad para invocar reflexivamente:

public static Object invoke(Object targetObject, String methodName, Object[] arguments, Class<?>[] signature) 

El funcionamiento básico es

return method.invoke(targetObject, arguments); 

Como una optimización del rendimiento, cacheamos el método que utiliza un hash de clase, nombre del método del objeto de destino y firma (cuyo código podría usar alguna mejora) pero más allá 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 aún 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).

Saludos.

Respuesta

49

Los comentarios a continuación se refieren a la implementación de Sun, en particular a 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 su 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.

+1

Guau, nunca lo supe. Haré esas pruebas de tiempo. ¿Hay alguna forma de anular el valor predeterminado 15? ¿Alguna otra forma de persuadir al compilador para que inicie temprano? – Nicholas

+8

Simplemente configure la propiedad sun.reflect.inflationThreshold en el número que prefiera. –

0

Optimización prematura es generalmente malo. 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).

+0

Concebido, pero en el proceso de ser un poco astuto, encontré que los métodos no públicos requieren escalar el árbol de herencia para encontrar métodos en los padres, así que encontré algunos casos requeridos para llamar al método [Declarado] varias veces. Además, solo tengo que llamar a setAccessible (verdadero) una vez. ¿Todavía estoy fuera de la base? – Nicholas

+0

No creo que estés fuera de la base, es un análisis interesante, pero a menos que encuentres una necesidad, siempre deberías usar la solución más fácil de leer, simple y directa que se te pueda ocurrir. –

1

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, puede querer aferrarse y reutilizar el objeto argumento de matriz (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 poco probable), puede colgar en los parms y (la matriz de argumentos con sus valores) reutilizarlos.

No intentaré nada más que por las razones ya dadas por las otras respuestas.

5

Realicé algunas pruebas en torno a la respuesta de Chris Jester-Young y utilizando las opciones detalladas, definitivamente observé que el compilador tomaba alguna medida con respecto a la invocación número 15. Es difícil decir si hay mucho diferencial de rendimiento sin una prueba más sofisticada, pero es persuasivo. Aquí está la salida:

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 la InvokeDynamic empresa no está atrayendo a muchos desarrolladores sobre la base de la reflexión de aceleración/eliminación.

Gracias Chris.

0

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.

+0

El costo porcentual dependería completamente de lo que ese método realmente esté haciendo. – Nicole

+0

@Renesis: Correcto. Ese es mi punto. El tiempo dedicado a entrar y salir del método puede ser pequeño en comparación con el tiempo dedicado a realizar un trabajo útil dentro del método. De ser así, optimizar la entrada y salida solo puede ayudar un poco, en porcentaje. –

+0

Mi punto es que si el método en sí era extremadamente ligero (una sola asignación o simplemente devuelve un valor) el tipo de método-llamada en sí mismo puede tener un gran impacto (agregado en muchas llamadas). – Nicole

Cuestiones relacionadas