Performance de reflection Java

La création d’un object en utilisant la reflection plutôt que d’appeler le constructeur de classe entraîne-t-elle des différences de performances significatives?

Oui absolument. La recherche d’une classe par reflection est, par ampleur , plus coûteuse.

Citant la documentation de Java sur la reflection :

Étant donné que la reflection implique des types résolus dynamicment, certaines optimisations de la machine virtuelle Java ne peuvent pas être effectuées. Par conséquent, les opérations de reflection ont des performances plus lentes que leurs homologues non réfléchissantes et doivent être évitées dans les sections de code appelées fréquemment dans les applications sensibles aux performances.

Voici un test simple que j’ai piraté en 5 minutes sur ma machine, exécutant Sun JRE 6u10:

public class Main { public static void main(Ssortingng[] 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); } } 

Avec ces résultats:

 35 // no reflection 465 // using reflection 

Gardez à l'esprit que la recherche et l'instanciation sont effectuées ensemble et que, dans certains cas, la recherche peut être refaite, mais ceci n'est qu'un exemple de base.

Même si vous instanciez juste, vous obtenez toujours un coup de performance:

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

Encore une fois, YMMV.

Oui, c’est plus lent.

Mais souvenez-vous de la foutue règle # 1 – L’OPTIMISATION DE LA PREMATURE EST LA RACINE DE TOUS LES MALADES

(Eh bien, peut être lié avec # 1 pour DRY)

Je jure que si quelqu’un venait me voir au travail et me demandait cela, je serais très vigilant sur leur code pour les prochains mois.

Vous ne devez jamais optimiser jusqu’à ce que vous soyez sûr que vous en avez besoin, jusque-là, écrivez simplement un code lisible.

Oh, et je ne veux pas dire non plus écrire un code stupide. Il suffit de penser à la manière la plus propre de le faire – pas de copier-coller, etc. (Méfiez-vous toujours des choses comme les boucles internes et utilisez la collection qui correspond le mieux à vos besoins – L’ignorance n’est pas une programmation “non optimisée” , c’est de la “mauvaise” programmation)

Cela me fait peur quand j’entends des questions comme celle-là, mais j’oublie que tout le monde doit apprendre toutes les règles avant de les comprendre. Vous l’aurez compris après avoir passé un mois-homme à déboguer quelque chose de “optimisé”.

MODIFIER:

Une chose intéressante s’est produite dans ce fil. Vérifiez la réponse # 1, c’est un exemple de la puissance du compilateur pour optimiser les choses. Le test est totalement invalide car l’instanciation non réfléchissante peut être complètement exclue.

Leçon? Ne jamais optimiser jusqu’à ce que vous ayez écrit une solution propre et soigneusement codée et que vous ayez prouvé qu’elle était trop lente.

Vous pouvez trouver que A a = new A () est optimisé par la JVM. Si vous placez les objects dans un tableau, ils ne fonctionnent pas aussi bien. 😉 Les copies suivantes …

 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(Ssortingng[] 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); } } 

Cela suggère que la différence est d'environ 150 ns sur ma machine.

“Significant” dépend entièrement du contexte.

Si vous utilisez la reflection pour créer un object de gestionnaire unique basé sur un fichier de configuration, puis passez le rest de votre temps à exécuter des requêtes de firebase database, cela est insignifiant. Si vous créez un grand nombre d’objects par reflection dans une boucle serrée, alors oui, c’est important.

En général, la flexibilité de conception (si nécessaire!) Devrait vous inciter à utiliser la reflection et non la performance. Toutefois, pour déterminer si les performances sont un problème, vous devez faire un profil plutôt que d’obtenir des réponses arbitraires à partir d’un forum de discussion.

S’il y a vraiment besoin de quelque chose de plus rapide que la reflection, et qu’il ne s’agit pas seulement d’une optimisation prématurée, alors la génération de bytecode avec ASM ou une bibliothèque de niveau supérieur est une option. La génération du bytecode la première fois est plus lente que l’utilisation de la reflection, mais une fois le bytecode généré, il est aussi rapide que le code Java normal et sera optimisé par le compilateur JIT.

Quelques exemples d’applications utilisant la génération de code:

  • Invoquer des méthodes sur les proxys générés par CGLIB est légèrement plus rapide que les proxies dynamics de Java, car CGLIB génère du bytecode pour ses mandataires, mais les proxies dynamics utilisent uniquement la reflection ( j’ai mesuré les appels de méthodes

  • JSerial génère un bytecode pour lire / écrire les champs des objects sérialisés, au lieu d’utiliser la reflection. Il existe des repères sur le site de JSerial.

  • Je ne suis pas sûr à 100% (et je n’ai pas envie de lire la source maintenant), mais je pense que Guice génère un bytecode pour faire l’dependency injection. Corrige moi si je me trompe.

Il y a un peu de rétention avec la reflection, mais c’est beaucoup plus petit sur les machines virtuelles modernes qu’auparavant.

Si vous utilisez la reflection pour créer chaque object simple de votre programme, alors quelque chose ne va pas. L’utiliser occasionnellement, quand vous avez de bonnes raisons, ne devrait pas poser de problème.

La reflection est lente, bien que l’atsortingbution des objects ne soit pas aussi désespérée que d’autres aspects de la reflection. Pour obtenir des performances équivalentes avec une instanciation basée sur la reflection, vous devez écrire votre code afin que jit puisse déterminer quelle classe est instanciée. Si l’identité de la classe ne peut pas être déterminée, le code d’allocation ne peut pas être inséré. Pire, l’parsing d’échappement échoue et l’object ne peut pas être affecté à la stack. Si vous êtes chanceux, le profil d’exécution de la JVM peut venir à la rescousse si ce code devient chaud et peut déterminer dynamicment quelle classe prédomine et peut optimiser pour celle-ci.

Sachez que les microbilles dans ce fil sont profondément défectueuses, alors prenez-les avec un grain de sel. Le moins défectueux est de loin celui de Peter Lawrey: il fait fonctionner l’échauffement pour obtenir les méthodes écrasées, et il (consciemment) défait l’parsing d’évasion pour s’assurer que les allocations se produisent réellement. Cependant, même celui-ci a ses problèmes: par exemple, on peut s’attendre à ce que le nombre énorme de banques de stockage vienne à bout des caches et des mémoires tampon, ce qui finira par constituer une référence mémoire si vos allocations sont très rapides. (Félicitations à Peter pour avoir bien compris la conclusion: la différence est “150ns” plutôt que “2.5x”. Je pense qu’il fait ce genre de choses pour gagner sa vie.)

Il est intéressant de noter que la configuration de setAccessible (true), qui ignore les contrôles de sécurité, a un coût réduit de 20%.

Sans setAccessible (true)

 new A(), 70 ns A.class.newInstance(), 214 ns new A(), 84 ns A.class.newInstance(), 229 ns 

Avec setAccessible (true)

 new A(), 69 ns A.class.newInstance(), 159 ns new A(), 85 ns A.class.newInstance(), 171 ns 

Oui, il existe un problème de performances lors de l’utilisation de Reflection, mais une solution de contournement possible pour l’optimisation consiste à mettre en cache la méthode:

  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"); 

aura pour résultat:

[java] La méthode de l'appelé fois fois avec la recherche a pris 5618 millis

[java] La méthode de l'appelé fois fois avec cache a pris 270 millis

Oui, c’est beaucoup plus lent. Nous utilisions du code qui faisait cela, et bien que je ne dispose pas des mésortingques disponibles pour le moment, nous avons dû refactoriser ce code pour ne pas utiliser la reflection. Si vous connaissez la classe, appelez directement le constructeur.

Dans doReflection () est la surcharge due à Class.forName (“misc.A”) (qui nécessiterait une recherche de classe, en analysant potentiellement le chemin de classe sur le système de fils), plutôt que le newInstance () appelle la classe. Je me demande à quoi ressembleraient les statistiques si Class.forName (“misc.A”) est effectué une seule fois en dehors de la boucle for, cela n’a pas vraiment à être fait pour chaque invocation de la boucle.

Oui, il sera toujours plus lent de créer un object par reflection, car la JVM ne peut pas optimiser le code au moment de la compilation. Consultez les didacticiels Sun / Java Reflection pour plus de détails.

Voir ce test simple:

 public class TestSpeed { public static void main(Ssortingng[] 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"); } } 

Souvent, vous pouvez utiliser les objects communs Apache BeanUtils ou PropertyUtils pour l’introspection (en fait, ils mettent en cache les métadonnées sur les classes afin qu’ils n’aient pas toujours besoin d’utiliser la reflection).

Je pense que cela dépend de la légèreté / lourdeur de la méthode cible. Si la méthode cible est très légère (par exemple, getter / setter), elle peut être 1 à 3 fois plus lente. Si la méthode cible prend environ 1 milliseconde ou plus, la performance sera très proche. voici le test que j’ai fait avec Java 8 et le réflectasme :

 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; } } } 

Le code de test complet est disponible sur GitHub: ReflectionTest.java