Je travaille sur une application et une approche de conception implique une utilisation extrêmement intensive de l' instanceof
opérateur. Bien que je sache que la conception OO essaie généralement d'éviter d'utiliser instanceof
, c'est une autre histoire et cette question est purement liée aux performances. Je me demandais s'il y avait un impact sur les performances? Est-ce aussi rapide que ==
?
Par exemple, j'ai une classe de base avec 10 sous-classes. Dans une seule fonction qui prend la classe de base, je vérifie si la classe est une instance de la sous-classe et exécute une routine.
L'une des autres façons dont j'ai pensé à le résoudre était d'utiliser une primitive entière "type id" à la place, et d'utiliser un masque de bits pour représenter les catégories des sous-classes, puis de faire une comparaison de masque de bits des sous-classes "type id" avec un masque constant représentant la catégorie.
Est en instanceof
quelque sorte optimisé par la JVM pour être plus rapide que cela? Je veux m'en tenir à Java mais les performances de l'application sont essentielles. Ce serait cool si quelqu'un qui avait déjà suivi cette voie pouvait offrir des conseils. Suis-je trop fatigué ou me concentre-t-il sur la mauvaise chose à optimiser?
Réponses:
Les compilateurs JVM / JIC modernes ont supprimé les performances de la plupart des opérations traditionnellement "lentes", y compris l'instance, la gestion des exceptions, la réflexion, etc.
Comme l'a écrit Donald Knuth, "Nous devons oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal". Les performances d'instanceof ne seront probablement pas un problème, alors ne perdez pas votre temps à trouver des solutions de rechange exotiques jusqu'à ce que vous soyez sûr que c'est le problème.
la source
try { ObjT o = (ObjT)object } catch (e) { no not one of these }
plus rapide plus lent ??Approche
J'ai écrit un programme de référence pour évaluer différentes implémentations:
instanceof
mise en œuvre (comme référence)@Override
une méthode de testgetClass() == _.class
la mise en oeuvreJ'ai utilisé jmh pour exécuter la référence avec 100 appels de préchauffage, 1000 itérations sous mesure et avec 10 fourchettes. Donc, chaque option a été mesurée 10 000 fois, ce qui prend 12 h 18 min 57 s pour exécuter l'intégralité de la référence sur mon MacBook Pro avec macOS 10.12.4 et Java 1.8. L'indice de référence mesure le temps moyen de chaque option. Pour plus de détails, voir mon implémentation sur GitHub .
Par souci d'exhaustivité: Il existe une version précédente de cette réponse et ma référence .
Résultats
tl; dr
En Java 1.8
instanceof
est l'approche la plus rapide, bien qu'ellegetClass()
soit très proche.la source
+0.(9)
pour la science!+1.0(9)
. :)System.currentTimeMillis()
d'une opération qui n'est pas beaucoup plus qu'un appel de méthode unique, ce qui devrait donner beaucoup à une faible précision. Utilisez plutôt un framework de référence tel que JMH !Je viens de faire un test simple pour voir comment les performances instanceOf se comparent à un simple appel s.equals () à un objet chaîne avec une seule lettre.
dans une boucle de 10 000 000, l'instanceOf m'a donné 63-96 ms, et la chaîne égale m'a donné 106-230 ms
J'ai utilisé java jvm 6.
Donc, dans mon test simple, il est plus rapide de faire une instanceOf au lieu d'une comparaison de chaînes à un caractère.
l'utilisation de .equals () d'Integer au lieu de chaîne m'a donné le même résultat, seulement quand j'ai utilisé le == i était plus rapide que instanceOf de 20 ms (dans une boucle de 10.000.000)
la source
equals()
ne le coupera pas, car le sous- classement ; vous avez besoinisAssignableFrom()
.Les éléments qui détermineront l'impact sur les performances sont les suivants:
J'ai créé une micro - référence pour quatre méthodes d'envoi différentes . Les résultats de Solaris sont les suivants, le plus petit nombre étant plus rapide:
la source
Répondre à votre toute dernière question: à moins qu'un profileur ne vous le dise, que vous passez des heures ridicules dans une instance de: Oui, vous êtes tatillonne.
Avant de vous interroger sur l'optimisation de quelque chose qui n'a jamais eu besoin d'être optimisé: Écrivez votre algorithme de la manière la plus lisible et exécutez-le. Exécutez-le jusqu'à ce que le compilateur jit ait la possibilité de l'optimiser lui-même. Si vous avez ensuite des problèmes avec ce morceau de code, utilisez un profileur pour vous dire où gagner le plus et l'optimiser.
En période d'optimisation élevée des compilateurs, vos suppositions sur les goulots d'étranglement seront probablement fausses.
Et dans le véritable esprit de cette réponse (que je crois de tout cœur): je ne sais absolument pas comment instanceof et == se rapportent une fois que le compilateur jit a eu la chance de l'optimiser.
J'ai oublié: ne mesurez jamais la première manche.
la source
J'ai la même question, mais parce que je n'ai pas trouvé de «mesures de performance» pour un cas d'utilisation similaire au mien, j'ai fait un exemple de code supplémentaire. Sur mon matériel et Java 6 et 7, la différence entre instanceof et allumer des itérations de 10 mln est
Ainsi, instanceof est vraiment plus lent, en particulier sur un grand nombre d'instructions if-else-if, mais la différence sera négligeable dans une application réelle.
la source
instanceof
est vraiment rapide, ne prenant que quelques instructions CPU.Apparemment, si une classe
X
n'a pas de sous-classes chargées (la JVM le sait), elleinstanceof
peut être optimisée comme suit:Le coût principal est juste une lecture!
Si
X
des sous-classes sont chargées, quelques lectures supplémentaires sont nécessaires; ils sont probablement colocalisés, de sorte que le coût supplémentaire est également très faible.Bonnes nouvelles tout le monde!
la source
foo
- mais estfoo
actuellement actuellement optimisé par javac / VM d'Oracle - ou est-il simplement possible qu'il le fasse à l'avenir? De plus, j'ai demandé au répondeur s'il a une source de support (que ce soit des documents, du code source, un blog de développement) documentant qu'il peut en effet être optimisé ou est optimisé ? Sans cela, cette réponse n'est qu'une réflexion au hasard sur ce que le compilateur peut éventuellement faire.Instanceof est très rapide. Il se résume à un bytecode qui est utilisé pour la comparaison de référence de classe. Essayez quelques millions d'instances dans une boucle et voyez par vous-même.
la source
instanceof va probablement être plus coûteux qu'un simple égal dans la plupart des implémentations du monde réel (c'est-à-dire celles où instanceof est vraiment nécessaire, et vous ne pouvez pas simplement le résoudre en remplaçant une méthode commune, comme tous les manuels pour débutants ainsi que Demian ci-dessus suggère).
Pourquoi donc? Parce que ce qui va probablement se produire, c'est que vous avez plusieurs interfaces, qui fournissent certaines fonctionnalités (disons, les interfaces x, y et z), et certains objets à manipuler qui peuvent (ou non) implémenter l'une de ces interfaces ... mais Pas directement. Disons, par exemple, que j'ai:
w étend x
A implémente w
B étend A
C étend B, implémente y
D étend C, implémente z
Supposons que je traite une instance de D, l'objet d. Le calcul (d instanceof x) nécessite de prendre d.getClass (), de parcourir les interfaces qu'il implémente pour savoir si l'on est == à x, et sinon de le faire récursivement pour tous leurs ancêtres ... Dans notre cas, si vous faites une première exploration approfondie de cet arbre, donne au moins 8 comparaisons, en supposant que y et z ne prolongent rien ...
La complexité d'un arbre de dérivation du monde réel est probablement plus élevée. Dans certains cas, le JIT peut optimiser la majeure partie de celui-ci s'il est capable de résoudre à l'avance d comme étant, dans tous les cas possibles, une instance de quelque chose qui étend x. De façon réaliste, cependant, vous allez parcourir cet arbre la plupart du temps.
Si cela devient un problème, je suggère d'utiliser une carte de gestionnaire à la place, reliant la classe concrète de l'objet à une fermeture qui gère. Il supprime la phase de traversée de l'arbre au profit d'une cartographie directe. Cependant, sachez que si vous avez défini un gestionnaire pour C.class, mon objet d ci-dessus ne sera pas reconnu.
voici mes 2 cents, j'espère qu'ils vous aideront ...
la source
instanceof est très efficace, il est donc peu probable que vos performances en souffrent. Cependant, l'utilisation de beaucoup d'instanceof suggère un problème de conception.
Si vous pouvez utiliser xClass == String.class, c'est plus rapide. Remarque: vous n'avez pas besoin de instanceof pour les classes finales.
la source
x.getClass() == Class.class
c'est la même chose quex instanceof Class
x
être,null
je suppose. (Ou selon ce qui est plus clair)Généralement, la raison pour laquelle l'opérateur "instanceof" est mal vu dans un cas comme celui-ci (où l'instanceof vérifie les sous-classes de cette classe de base) est que ce que vous devriez faire est de déplacer les opérations dans une méthode et de la surcharger pour la méthode appropriée. sous-classes. Par exemple, si vous avez:
Vous pouvez remplacer cela par
puis avoir l'implémentation de "doEverything ()" dans l'appel de classe 1 "doThis ()", et dans l'appel de classe 2 "doThat ()", et ainsi de suite.
la source
'instanceof' est en fait un opérateur, comme + ou -, et je pense qu'il a sa propre instruction de bytecode JVM. Cela devrait être très rapide.
Je ne devrais pas dire que si vous avez un commutateur où vous testez si un objet est une instance d'une sous-classe, alors votre conception devra peut-être être retravaillée. Pensez à pousser le comportement spécifique à la sous-classe vers le bas dans les sous-classes elles-mêmes.
la source
Demian et Paul mentionnent un bon point; cependant , le placement du code à exécuter dépend vraiment de la façon dont vous souhaitez utiliser les données ...
Je suis un grand fan de petits objets de données qui peuvent être utilisés de nombreuses façons. Si vous suivez l'approche prioritaire (polymorphe), vos objets ne peuvent être utilisés que "à sens unique".
C'est là que les modèles entrent en jeu ...
Vous pouvez utiliser la double répartition (comme dans le modèle de visiteur) pour demander à chaque objet de «vous appeler» en passant lui-même - cela résoudra le type de l'objet. Cependant (encore une fois), vous aurez besoin d'une classe qui peut "faire des choses" avec tous les sous-types possibles.
Je préfère utiliser un modèle de stratégie, où vous pouvez enregistrer des stratégies pour chaque sous-type que vous souhaitez gérer. Quelque chose comme ce qui suit. Notez que cela n'aide que pour les correspondances de types exacts, mais présente l'avantage d'être extensible - les contributeurs tiers peuvent ajouter leurs propres types et gestionnaires. (C'est bon pour les frameworks dynamiques comme OSGi, où de nouveaux bundles peuvent être ajoutés)
J'espère que cela inspirera d'autres idées ...
la source
J'écris un test de performance basé sur jmh-java-benchmark-archetype: 2.21. JDK est openjdk et la version est 1.8.0_212. La machine de test est mac pro. Le résultat du test est:
Le résultat montre que: getClass est meilleur que instanceOf, ce qui est contraire aux autres tests. Mais je ne sais pas pourquoi.
Le code de test est ci-dessous:
la source
Il est difficile de dire comment une certaine machine virtuelle Java implémente une instance de, mais dans la plupart des cas, les objets sont comparables aux structures et les classes le sont aussi et chaque structure d'objet a un pointeur sur la structure de classe dont elle est une instance. Donc, en fait, instanceof pour
peut être aussi rapide que le code C suivant
en supposant qu'un compilateur JIT est en place et fait un travail décent.
Considérant qu'il s'agit uniquement d'accéder à un pointeur, d'obtenir un pointeur à un certain décalage vers lequel le pointeur pointe et de le comparer à un autre pointeur (ce qui est fondamentalement le même que de tester des nombres 32 bits égaux), je dirais que l'opération peut réellement soyez très rapide.
Cela n'est pas nécessaire, cependant, cela dépend beaucoup de la JVM. Cependant, si cela s'avérait être l'opération de goulot d'étranglement dans votre code, je considérerais l'implémentation JVM plutôt médiocre. Même celui qui n'a pas de compilateur JIT et qui n'interprète que du code devrait pouvoir faire un test instanceof en un rien de temps.
la source
Je reviendrai vers vous sur l'instance de performance. Mais un moyen d'éviter tout problème (ou son absence) serait de créer une interface parent pour toutes les sous-classes sur lesquelles vous devez faire instanceof. L'interface sera un super ensemble de toutes les méthodes dans les sous-classes pour lesquelles vous devez faire instanceof check. Lorsqu'une méthode ne s'applique pas à une sous-classe spécifique, fournissez simplement une implémentation factice de cette méthode. Si je n'ai pas mal compris le problème, c'est ainsi que j'ai résolu le problème par le passé.
la source
InstanceOf est un avertissement de mauvaise conception orientée objet.
Les machines virtuelles Java actuelles signifient que l' instanceOf n'est pas vraiment un problème de performances en soi. Si vous vous retrouvez à l'utiliser beaucoup, en particulier pour les fonctionnalités de base, il est probablement temps de regarder la conception. Les gains de performances (et de simplicité / maintenabilité) d'une refactorisation vers une meilleure conception l'emportent largement sur les cycles de processeur réels dépensés sur l' appel instanceOf réel .
Pour donner un très petit exemple de programmation simpliste.
Si une architecture était médiocre, un meilleur choix aurait été d'avoir SomeObject comme classe parent de deux classes enfants où chaque classe enfant remplace une méthode (doSomething) pour que le code se présente comme tel:
la source
Dans la version Java moderne, l'opérateur instanceof est plus rapide comme un simple appel de méthode. Ça signifie:
est plus rapide car:
Une autre chose est de savoir si vous devez mettre en cascade plusieurs instances. Ensuite, un commutateur qui n'appelle qu'une fois getType () est plus rapide.
la source
Si la vitesse est votre seul objectif, l'utilisation de constantes int pour identifier les sous-classes semble réduire de quelques millisecondes le temps
conception OO terrible, mais si votre analyse des performances indique que c'est là que se trouve votre goulot d'étranglement, alors peut-être. Dans mon code, le code d'expédition prend 10% du temps d'exécution total et cela peut contribuer à une amélioration de la vitesse totale de 1%.
la source
Vous devez mesurer / profiler s'il s'agit vraiment d'un problème de performance dans votre projet. Si c'est le cas, je recommanderais une refonte - si possible. Je suis presque sûr que vous ne pouvez pas battre l'implémentation native de la plateforme (écrite en C). Vous devez également prendre en compte l'héritage multiple dans ce cas.
Vous devriez en dire plus sur le problème, peut-être pourriez-vous utiliser un magasin associatif, par exemple une Map <Classe, Objet> si vous n'êtes intéressé que par les types concrets.
la source
En ce qui concerne la note de Peter Lawrey selon laquelle vous n'avez pas besoin d'instanceof pour les classes finales et pouvez simplement utiliser une égalité de référence, soyez prudent! Même si les classes finales ne peuvent pas être étendues, elles ne sont pas garanties d'être chargées par le même chargeur de classe. N'utilisez x.getClass () == SomeFinal.class ou son acabit si vous êtes absolument certain qu'il n'y a qu'un seul chargeur de classe en jeu pour cette section de code.
la source
Je préfère également une approche enum, mais j'utiliserais une classe de base abstraite pour forcer les sous-classes à implémenter la
getType()
méthode.la source
J'ai pensé qu'il pourrait être utile de soumettre un contre-exemple au consensus général sur cette page selon lequel "instanceof" n'est pas assez cher à craindre. J'ai trouvé que j'avais du code dans une boucle interne qui (dans une tentative historique d'optimisation)
où l'appel de head () sur un SingleItem renvoie la valeur inchangée. Remplacement du code par
me donne une accélération de 269 ms à 169 ms, malgré le fait qu'il se passe des choses assez lourdes dans la boucle, comme la conversion de chaîne en double. Il est bien sûr possible que l'accélération soit davantage due à l'élimination de la branche conditionnelle qu'à l'élimination de l'opérateur instanceof lui-même; mais j'ai pensé qu'il valait la peine de le mentionner.
la source
if
lui - même. Si la distribution detrue
s etfalse
s est proche de pair, l'exécution spéculative devient inutile, ce qui entraîne des décalages importants.Vous vous concentrez sur la mauvaise chose. La différence entre instanceof et toute autre méthode pour vérifier la même chose ne serait probablement même pas mesurable. Si les performances sont critiques, Java est probablement le mauvais langage. La principale raison étant que vous ne pouvez pas contrôler le moment où la machine virtuelle décide d'aller récupérer les ordures, ce qui peut prendre le processeur à 100% pendant plusieurs secondes dans un grand programme (MagicDraw 10 était parfait pour cela). À moins que vous ne contrôliez chaque ordinateur sur lequel ce programme s'exécutera, vous ne pouvez pas garantir sur quelle version de la machine virtuelle Java il sera activé, et bon nombre des anciennes versions avaient des problèmes de vitesse majeurs. Si c'est une petite application , vous pouvez être ok avec Java, mais si vous lisez en permanence et jeter les données alors vous allez remarquer lorsque les coups de pied de GC dans.
la source