L'impact sur les performances de l'utilisation d'instanceof en Java

315

Je travaille sur une application et une approche de conception implique une utilisation extrêmement intensive de l' instanceofopé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 instanceofquelque 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?

Josh
la source
81
Je pense que le point de la question, cependant, était de mettre de côté la question des meilleures pratiques OO, et d'examiner la performance.
Dave L.
3
@Dave L. Normalement, je serais d'accord, mais l'OP mentionne qu'il cherche des techniques d'optimisation générales et il n'est pas sûr que son problème soit lié à "instanceof". Je pense qu'il vaut au moins la peine de mentionner la conception «correcte» pour qu'il puisse profiler les deux choix.
Programmeur hors
51
Ugh ... Pourquoi toutes les réponses manquent le point de la question et fournissent la même vieille rhétorique Knuth sur l'optimisation? Votre question est de savoir si instanceof est significativement / étonnamment plus lent que de vérifier l'objet classe avec ==, et j'ai trouvé que ce n'était pas le cas.
gubby
3
Les performances d'instanceof et de casting sont assez bonnes. J'ai posté un timing dans Java7 autour de différentes approches du problème ici: stackoverflow.com/questions/16320014/…
Wheezil

Réponses:

268

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.

Steve
la source
13
JVM / JIC moderne ..Pouvez-vous s'il vous plaît mentionner de quelle version java ces optimisations ont été couvertes?
Ravisha
138
Il y a toujours quelqu'un, qui cite Knuth lorsque la performance est le sujet ... Oubliant, que Knuth a également déclaré (dans le même article) "Dans les disciplines d'ingénierie établies, une amélioration de 12%, facilement obtenue, n'est jamais considérée comme marginale et je crois que le même point de vue devrait prévaloir en génie logiciel ", presque tout son travail portait sur l'efficacité des algorithmes et il a écrit des algorithmes en assemblage pour (entre autres) obtenir de meilleures performances. Meh ...
kgadek
4
Un aparté ici mais serait try { ObjT o = (ObjT)object } catch (e) { no not one of these }plus rapide plus lent ??
peterk
35
Si "object" est une instance d'ObjT, le lancer est un peu plus rapide que de faire une instanceof, mais la différence que mon test rapide a trouvée était de 10-20 ms sur 10 000 000 d'itérations. Si "objet" n'est pas un ObjT, cependant, la capture de l'exception était plus de 3000x plus lente - plus de 31 000 ms contre ~ 10 ms pour l'instanceof.
Steve
19
un argument si fort sans aucune "référence", est complètement inutile car juste opinionné.
marcorossi
280

Approche

J'ai écrit un programme de référence pour évaluer différentes implémentations:

  1. instanceof mise en œuvre (comme référence)
  2. orienté objet via une classe abstraite et @Overrideune méthode de test
  3. en utilisant une implémentation de type propre
  4. getClass() == _.class la mise en oeuvre

J'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

| Fonctionnement | Autonomie en nanosecondes par opération | Par rapport à instanceof |
| ------------ | ------------------------------------ - | ------------------------ |
| INSTANCEOF | 39 598 ± 0,022 ns / op | 100,00% |
| GETCLASS | 39 687 ± 0,021 ns / op | 100,22% |
| TYPE | 46 295 ± 0,026 ns / op | 116,91% |
| OO | 48 078 ± 0,026 ns / op | 121,42% |

tl; dr

En Java 1.8 instanceofest l'approche la plus rapide, bien qu'elle getClass()soit très proche.

Michael Dorner
la source
58
+0.(9)pour la science!
16
+ l'autre 0,1 de moi: D
Tobias Reich
14
@TobiasReich Nous avons donc obtenu +1.0(9). :)
Pavel
9
Je ne pense pas que cela mesure quoi que ce soit de significatif du tout. Le code mesure l'utilisation 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 !
Lii
6
Ou faites simplement le timing du milliard d'appels plutôt que par appel.
LegendLength
74

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
4
Serait-il possible pour vous de poster le code ici? Ce serait génial!
The Alchemist
7
Comment instanceOf se compare-t-il à la répartition des fonctions polymorphes?
Chris
21
Pourquoi comparez-vous instanceof avec un String.equals ()? Si vous voulez vérifier le type que vous devez object.getClass (). Equals (SomeType.class)
marsbear
4
@marsbear equals()ne le coupera pas, car le sous- classement ; vous avez besoin isAssignableFrom().
David Moles
1
@marsbear C'est vrai, mais ce n'est pas un meilleur test de ce que le PO demandait.
David Moles
20

Les éléments qui détermineront l'impact sur les performances sont les suivants:

  1. Le nombre de classes possibles pour lesquelles l'opérateur instanceof pourrait retourner true
  2. La distribution de vos données - la plupart des instances d'opérations sont-elles résolues lors de la première ou de la deuxième tentative? Vous voudrez mettre vos plus susceptibles de renvoyer de véritables opérations en premier.
  3. L'environnement de déploiement. L'exécution sur une machine virtuelle Sun Solaris est très différente de la machine virtuelle Java Windows de Sun. Solaris s'exécutera en mode «serveur» par défaut, tandis que Windows s'exécutera en mode client. Les optimisations JIT sur Solaris rendront tous les accès aux méthodes identiques.

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:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
brianegge
la source
18

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.

Olaf Kock
la source
1
Mais l'affiche originale mentionnait que les performances étaient essentielles pour cette application, il n'est donc pas déraisonnable d'optimiser tôt dans cette situation. En d'autres termes, vous n'écririez pas un jeu 3D dans GWBasic, puis à la fin, ok, commençons à l'optimiser, la première étape consiste à le porter en c ++.
LegendLength
GWBasic peut être un bon début pour les jeux 3D, si des bibliothèques appropriées sont disponibles. Mais cela mis à part (car c'est un argument artificiel): OP ne demande pas une réécriture complète comme optimisation. Il s'agit d'une construction unique où nous ne savons même pas si l'impact est significatif (même s'il existe un moyen plus performant de faire de même dans la version actuelle du compilateur ). Je suis fermement derrière c2.com/cgi/wiki?ProfileBeforeOptimizing et ma réponse. L'optimisation préliminaire est la racine de tout mal! Cela rend la maintenance plus difficile - et la maintenance est l'aspect qui mérite d'être optimisé
Olaf Kock
15

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

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

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.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}
Xtra Coder
la source
Quel résultat était java 6 et quel était java 7? Avez-vous revisité cela sous Java 8? Plus important ici, vous comparez une longueur, si instanceofs, à ce qui est essentiel une déclaration de cas sur ints. Je pense que nous nous attendrions à ce qu'un commutateur int s'allume rapidement.
Azeroth2b
1
Je ne me souviens pas exactement de ce qui se passait il y a 5 ans - je pense que Java 6 et Java 7 ont eu un résultat similaire, c'est pourquoi il n'y a qu'un seul résultat fourni (à condition que 2 lignes soient pour une profondeur de hiérarchie de classe différente) ... et non , je n'ai pas essayé de comparer avec Java 8. L'ensemble du code de test est fourni - vous pouvez le copier / coller et vérifier dans les environnements dont vous avez besoin (note - de nos jours j'utiliserais le test JMH pour cela).
Xtra Coder
9

instanceof est vraiment rapide, ne prenant que quelques instructions CPU.

Apparemment, si une classe Xn'a pas de sous-classes chargées (la JVM le sait), elle instanceofpeut être optimisée comme suit:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

Le coût principal est juste une lecture!

Si Xdes 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!

irréprochable
la source
2
peut être optimisé ou est optimisé? la source?
@vaxquis peut comme son implément jvm spécifique
RecursiveExceptionException
@itzJanuary soupire que vous avez manqué le point de ma question ici: tout le monde sait que le compilateur peut optimiser foo- mais est fooactuellement 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.
@vaxquis Vous n'avez jamais mentionné la VM Hotspot mais dans ce cas je ne sais pas si elle est "optimisée".
RecursiveExceptionException
1
Lisez récemment que JIT (JVM 8) optimisera un site d'appel pour 1 ou 2 types par appels directs, mais revient à la table virtuelle si plus de deux types réels sont rencontrés. Ainsi, le fait d'avoir uniquement deux types concrets passant par un site d'appel au moment de l'exécution représente un avantage de performance.
simon.watts
5

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.

Apocalisp
la source
5

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
5

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.

Peter Lawrey
la source
1
Btw qu'entendez-vous par "pas besoin d'instanceof pour les classes finales"?
Pacerier
Une classe finale ne peut pas avoir de sous-classes. Dans ce cas, x.getClass() == Class.classc'est la même chose quex instanceof Class
Peter Lawrey
Cool, en supposant que x n'est pas nul, lequel préféreriez-vous?
Pacerier
Bon point. Cela dépendrait de ce que je pense xêtre, nullje suppose. (Ou selon ce qui est plus clair)
Peter Lawrey
Hmm, je viens de réaliser que nous pourrions également utiliser java.lang.class.isAssignableFrom, savez-vous si instanceof keyword utilise en interne des fonctions comme celles-ci?
Pacerier
4

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:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

Vous pouvez remplacer cela par

o.doEverything();

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.

Paul Tomblin
la source
11
Mais parfois, vous ne pouvez pas. Si vous implémentez une interface qui vous permet de prendre un objet et que vous devez dire de quel type il s'agit, alors instanceof est vraiment la seule option. Vous pouvez essayer de lancer un casting, mais instanceof est généralement plus propre.
Herms
4

'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.

Programmeur hors-la-loi
la source
4

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 ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}
Scott Stanchfield
la source
4

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:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

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:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}
salexinx
la source
Si je devais spéculer, ce que fait instanceof est sans doute plus complexe. Une vérification getClass () == effectuera une vérification précise de 1: 1, où instanceof vérifie une hiérarchie, c'est-à-dire que myHashSet instanceof Collection passerait, mais myHashSet.getClass () == Collection.class ne le ferait pas. Essentiellement, ce ne sont pas des opérations équivalentes, donc je ne suis pas trop surpris que les performances soient également différentes.
AMTerp
3

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

if (o instanceof java.lang.String)

peut être aussi rapide que le code C suivant

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

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.

Mecki
la source
1
N'est-il pas nécessaire de déterminer si o hérite de java.lang.String?
WW.
1
C'est pourquoi j'ai dit que cela "pourrait" être aussi rapide. En réalité, il effectue une boucle, vérifiant d'abord iAmInstanceOf par rapport à la classe en question, puis il remonte l'arborescence d'héritage de o et répète cette vérification pour chaque super-classe de o (il lui faudra donc peut-être exécuter cette boucle plusieurs fois pour un match)
Mecki
3

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é.

Jose Quijada
la source
2

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.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

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:

Someobject.doSomething();
Demian Krige
la source
61
Je suis au courant de ça. Ce n'est pas ce que j'ai demandé.
Josh
Je ne sais
7
Je pense que l'exemple de code est en fait très mauvais: vous ne pouvez pas étendre la classe Double, et vous ne pouvez pas non plus dériver Double d'une autre classe. Si vous aviez utilisé d'autres classes pour l'exemple, cela aurait été correct.
Lena Schimmel
6
De plus, si les classes enfants de SomeObject sont des objets de valeur, vous ne voulez pas y mettre la logique. Par exemple, Pie and Roast peut ne pas être le bon endroit pour la logique putInOven () et putInMouth ().
sk.
une tarte et un rôti auto
cuisinés
2

Dans la version Java moderne, l'opérateur instanceof est plus rapide comme un simple appel de méthode. Ça signifie:

if(a instanceof AnyObject){
}

est plus rapide car:

if(a.getType() == XYZ){
}

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.

Horcrux7
la source
1

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

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

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%.

Salix alba
la source
0

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.

Karl
la source
0

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
4
Si une classe est chargée par un chargeur de classe différent, je ne pense pas que instanceof correspondra non plus.
Peter Lawrey
0

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.

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}
Mike
la source
0

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)

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

où l'appel de head () sur un SingleItem renvoie la valeur inchangée. Remplacement du code par

seq = seq.head();

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.

Michael Kay
la source
Cela pourrait être dû à iflui - même. Si la distribution de trues et falses est proche de pair, l'exécution spéculative devient inutile, ce qui entraîne des décalages importants.
Dmytro
-4

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.

tloach
la source
7
Cela est beaucoup moins vrai des algorithmes de collecte de déchets Java plus modernes qu'auparavant. Même les algorithmes les plus simples ne se soucient plus de la quantité de mémoire que vous jetez juste après l'avoir utilisée - ils ne se soucient que de la quantité conservée dans les collections de la jeune génération.
Bill Michell
3
Génial, sauf que je suis sur la JVM la plus récente et que mon ordinateur continue de fonctionner lorsque le GC s'exécute. Sur un serveur RAM double cœur de 3 Go. Java n'est pas un langage à utiliser si les performances sont réellement importantes.
tloach
@David: Vous n'avez pas besoin d'exiger en temps réel des problèmes lorsque votre application disparaît pendant une certaine période. Une application amusante que j'ai rencontrée est une application java qui s'est connectée à un flux TCP qui est mort lorsque le GC s'est exécuté car il n'a pas fermé le flux en premier et ne pouvait pas gérer la surcharge du trafic réseau à son retour - il le ferait immédiatement entrer dans une boucle où GC s'exécute, lorsque l'application reprend, elle essaie de parcourir un tas de données, ce qui l'a fait manquer de mémoire, ce qui a déclenché le GC, etc. Java est idéal pour beaucoup de tâches, mais pas pour les tâches très une bonne performance est une exigence.
tloach
6
@tloach me semble être une mauvaise conception d'application. vous parlez de "performance" comme si c'était unidimensionnel. J'ai travaillé avec (et sur) de nombreuses applications Java qui étaient, par exemple, performantes pour fournir une analyse statistique interactive et une visualisation rapides de très grands ensembles de données, ou performantes pour traiter très rapidement de très gros volumes de transactions. "performance" n'est pas seulement une chose, et le fait que quelqu'un puisse mal écrire une application qui gère mal la mémoire et laisse GC se débrouiller à sa manière ne signifie pas que quelque chose nécessitant "performance" devrait être écrit autrement.
David Moles