7
votes

Problème de performance de réflexion Java

Je sais qu'il y a beaucoup de sujets à parler de performances de réflexion.

Même officiel Java Docs dit que la réflexion est plus lente, mais j'ai ce code: p> xxx pré>

} P>

Je ne pense pas être une référence valide, mais au moins montrer une certaine différence. J'ai exécuté l'attente d'attendre de voir les appels normaux de réflexion étant un peu plus lents que les réguliers. P>

mais cela imprime ceci: p> xxx pré>

juste à note, d'abord Je l'ai exécuté sans ce groupe de sysouts, puis j'ai réalisé que certaines optimisation JVM ne font que la rendre plus rapide, alors j'ai ajouté ces imprimés pour voir si la réflexion était encore plus rapide. P>

Le résultat sans système est: P>

100000 regular method calls:70 milliseconds.
100000 reflective method calls without lookup:120 milliseconds.
100000 reflective method calls with lookup:129 milliseconds.


1 commentaires

Avez-vous le même résultat quel que soit les tests exécutés en premier? Ou mieux, séparé en 3 points?


8 Réponses :


12
votes

Ne jamais tester les performances Différents bits de code dans la même "course". Le JVM dispose de diverses optimisations qui le veulent que le résultat final est identique, comment les internes sont effectués peut différer. En termes plus concrètes, lors de votre test, le JVM peut avoir remarqué que vous appelez beaucoup d'objet.Tostring beaucoup et ont commencé à aligner les appels de méthode vers Object.Tostring. Il a peut-être commencé à effectuer une boucle se dérouler. Ou il aurait pu être une collection d'ordures dans la première boucle mais pas la deuxième ou la troisième boucle.

Pour obtenir une image plus significative, mais toujours pas une image totalement précise, vous devez séparer votre test en trois programmes distincts.

Les résultats sur mon ordinateur (sans impression et 1 000 000 exécutions chacun)

Les trois boucles fonctionnent dans le même programme

1000000 Méthode régulière Appels: 490 millisecondes.

1000000 Méthode de réflexion Appels sans recherche: 393 millisecondes.

Méthode de réflexion de 1000000 Appels de réflexion avec Loopup: 978 millisecondes.

Les boucles fonctionnent dans des programmes distincts

1000000 Méthode régulière Appels: 475 millisecondes.

1000000 Méthode de réflexion Appels sans recherche: 555 millisecondes.

Méthode de réflexion de 1000000 Appels de réflexion avec Loopup: 1160 millisecondes.


0 commentaires

5
votes

Il y a un Article de Brian Goetz sur Microbenchmarks cela vaut la peine de lire. On dirait que tu ne fais rien pour réchauffer la JVM (ce qui signifie que cela donne une chance de faire tout ce qui l'a incroyable ou d'autres optimisations qu'il va faire) avant de faire vos mesures, il est donc probable que le test non réfléchissant ne soit toujours pas réchauffé encore, et cela pourrait incliner vos chiffres.


0 commentaires

3
votes

Les micro-repères telles que ceci ne seront jamais exactes du tout - car la machine virtuelle "réchauffe" il s'agira de bits de code et d'optimiser les morceaux de code tel qu'il va, donc la même chose exécutée 2 minutes dans un programme pourrait beaucoup surperformer le bien au début.

En termes de ce qui se passe ici, je suppose que le premier bloc d'appel de méthode "normal" se réchauffe, de sorte que les blocs réfléchissants (et bien tous les appels ultérieurs) seraient plus rapides. La seule surcharge a été ajoutée en appelant de manière réfléchi une méthode que je peux constater consiste à rechercher le pointeur sur cette méthode, qui est une opération à l'échelle nanoseconde de toute façon et serait facilement mis en cache par la JVM. Le reste serait sur la manière dont la machine virtuelle est réchauffée, ce qui est au moment où vous atteignez les appels réfléchissants.


1 commentaires

Hmm .. J'ai fait ce test séparé, et c'est vraiment différent.



2
votes

J'ai écrit mon propre micro-repère, sans boucles, et avec system.nanotime () : xxx

J'ai exécuté cela dans Eclipse sur Ma machine une douzaine de fois, et les résultats varient un peu, mais voici ce que je reçois généralement:

  • Les horloges d'invocation de la méthode directe à 40-50 microsecondes
  • Horloges de recherche de méthodes à 150-200 microsecondes
  • Invocation réfléchissante avec les horloges de la variable de méthode à 250-310 microsecondes.

    Maintenant, n'oubliez pas les mises en garde sur les microbenches décrites dans la réponse de Nathan - il y a certainement beaucoup de défauts dans ce micro de référence - et faites confiance à la documentation s'ils disent que la réflexion est beaucoup plus lente que l'invocation directe.


1 commentaires

Votre implémentation pose un certain nombre de problèmes. Premièrement, Nanotime dans tous les VMS que je sais n'est que précis avec un microseconde. Deuxièmement, une grande partie du temps dans chaque référence sera consacrée à la recherche du temps, pas la méthode réelle que vous souhaitez tester - une raison pour laquelle les boucles sont effectuées dans la répétition. Troisièmement, avec une seule invocation, vous testez le temps nécessaire pour exécuter le code d'octet en mode interprété par opposition au mode compilé (rendu plus lentement qu'il n'était autrement si cette méthode était critique).



4
votes

Lorsque vous avez plusieurs boucles de course longues, la première boucle peut déclencher la méthode pour compiler, ce qui entraîne les boucles ultérieures étant optimisées dès le début. Cependant, l'optimisation peut être sous-optimale car elle n'a aucune information d'exécution pour ces boucles. Le Tostring est relativement coûteux et le couple prend plus de temps que les appels de réflexions.

Vous n'avez pas besoin de programmes distincts pour éviter une optimisation de la boucle en raison d'une boucle antérieure. Vous pouvez les exécuter dans différentes méthodes.

Les résultats que je reçois sont xxx

xxx / p>


0 commentaires

3
votes

Il n'y a pas de raison inhérente à la raison pour laquelle l'appel réfléchissant devrait être plus lent qu'un appel normal. JVM peut les optimiser dans la même chose.

Pratiquement, les ressources humaines sont limitées et elles devaient d'abord optimiser les appels normaux. Au fil du temps, ils peuvent travailler sur optimiser les appels réfléchissants; surtout quand la réflexion devient de plus en plus populaire.


0 commentaires

2
votes

Cela me frappe que vous avez placé un "system.out.println (s)" appelle à l'intérieur de votre boucle de référence interne. Puisque Effectuer IO est obligé d'être lent, il "avale" votre référence et la surcharge de l'invoke devient négligeable.

Essayez de supprimer le code "println ()" Appeler et exécuter le code comme celui-ci, je suis sûr que vous " D Soyez surpris par le résultat (certains des calculs idius sont nécessaires pour éviter le compilateur optimisant les appels complètement): xxx

- TR >


0 commentaires

1
votes

Même si vous recherchez la méthode dans les deux cas (c'est-à-dire avant la 2e et 3ème boucle), La première recherche prend la place moins de temps que la seconde recherche, qui aurait dû être l'inverse et moins qu'une méthode régulière d'appel sur ma machine.

Néanmoins, si vous utilisez la 2e boucle avec la recherche de méthode et System.out.println Déclaration, je reçois ceci: xxx

sans system.out.println déclaration, je reçois: xxx


3 commentaires

Je ne pense pas que l'affiche originale envisageait de réinvoyer la réflexion dans une boucle. Les applications à base de réflexion font souvent la réflexion une fois (elle ne change pas), puis utilisez les instances en cache pour effectuer l'invocation.


Oui, Steve, je viens de remarquer ça. J'étais au milieu de l'édition du post lorsque vous avez posté votre réponse et je ne suis pas sûr que mon poste édité a été perdu quelque part.


Passer par des postes ici, je ne suis pas sûr que si une opinion concluante a été faite, mais à mon avis, sur la base de la performance sur ma machine avec des boucles INT = 100 000 000; (Et pas de système.out.println ()) Call régulier: 12 800 ms, recherchez (1ère heure, 2e boucle): 12 200 ms et rechercher (2e fois, 3e boucle): 61, 600 ms et donc pour les méthodes à faible coût La réflexion n'aurait probablement aucun coût de performance à supporter.