web-dev-qa-db-fra.com

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

Je travaille sur une application et une approche de conception implique une utilisation extrêmement intensive de l'opérateur instanceof. Bien que je sache que OO design tente généralement d’éviter d’utiliser instanceof, il en va tout autrement et cette question est purement liée à la performance. 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 fonction unique 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’un des autres moyens que j’ai envisagé de résoudre consiste à utiliser une primitive entière "type id" et à utiliser un masque binaire pour représenter les catégories des sous-classes. masque constant représentant la catégorie.

Est-ce que instanceof est 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à emprunté cette voie pouvait donner quelques conseils. Suis-je trop nerveux ou ne me concentre pas sur la mauvaise chose à optimiser?

282
Josh

Les compilateurs JVM/JIC modernes ont éliminé l'impact négatif sur les performances de la plupart des opérations traditionnellement "lentes", notamment l'instance de, la gestion des exceptions, la réflexion, etc.

Comme Donald Knuth l'a écrit: "Nous devrions oublier les petites économies, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal." La performance d’instanceof ne sera probablement pas un problème, alors ne perdez pas votre temps à proposer des solutions de rechange exotiques avant d’être certain du problème.

246
Steve

Approche

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

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

J'ai utilisé jmh pour exécuter la référence avec 100 appels d’échauffement, 1000 itérations en cours de mesure et avec 10 fourchettes. Ainsi, chaque option a été mesurée 10 000 fois, ce qui prend 12h18:57 pour exécuter l’ensemble des critères de référence sur mon MacBook Pro avec macOS 10.12.4 et Java 1.8. Le repère 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 mon repère

Résultats

 Opération | Durée d'exécution en nanosecondes par opération | Relatif à 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, instanceof est l'approche la plus rapide, bien que getClass() soit très proche. 

230
Michael Dorner

Je viens de faire un test simple pour voir comment la performance instanceOf est comparée à un simple appel s.equals () à un objet string avec une seule lettre.

dans une boucle 10.000.000 l'instanceOf m'a donné 63-96ms et la chaîne égale m'a donné 106-230ms

J'ai utilisé Java JVM 6.

Donc, dans mon test simple, il est plus rapide de faire une instanceOf au lieu d'une comparaison d'une chaîne de caractères.

utiliser .equals () au lieu de string m'a donné le même résultat, mais seulement lorsque j'ai utilisé le == i était plus rapide que instanceOf de 20 ms (dans une boucle 10.000.000)

72
Dan Mihai Ile

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

  1. Le nombre de classes possibles pour lesquelles l'opérateur d'instanceof pourrait renvoyer true
  2. La distribution de vos données - la plupart des opérations d'instance d'opérations sont-elles résolues lors de la première ou de la deuxième tentative? Vous aurez envie de mettre votre plus susceptible de retourner les opérations vraies en premier.
  3. L'environnement de déploiement. L'exécution sur une Sun Solaris VM est très différente de la JVM 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 toutes les méthodes accessibles identiques. 

J'ai créé un microbenchmark 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 
17
brianegge

Répondez à votre toute dernière question: à moins que votre profileur ne vous dise que vous passez une quantité ridicule de temps dans une instance de: Oui, vous êtes en train de chercher.

Avant de vous interroger sur l'optimisation de quelque chose qui n'a jamais été 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 rencontrez ensuite des problèmes avec ce morceau de code, utilisez un profileur pour vous dire où obtenir le plus et l'optimiser.

À une époque où les compilateurs sont très optimistes, vos suppositions sur les goulots d'étranglement seront probablement totalement fausses.

Et dans l’esprit de cette réponse (à laquelle je crois sincèrement): je ne sais absolument pas comment relation d’instanceof et == une fois que le compilateur jit a eu la possibilité de l’optimiser.

J'ai oublié: ne mesurez jamais la première manche.

16
Olaf Kock

J'ai la même question, mais comme je n'ai pas trouvé de «métrique de performance» similaire à la mienne, j'ai créé quelques exemples de code. Sur mon matériel et Java 6 & 7, la différence entre instanceof et activer 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 pour un grand nombre d'énoncés si-sinon-si, mais la différence sera négligeable dans le cadre d'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()));
    }
}
12
Xtra Coder

instanceof est très rapide, ne prenant que quelques instructions du processeur.

Apparemment, si une classe X n'a pas de sous-classes chargées (JVM le sait), instanceof peut être optimisé comme suit:

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

Le coût principal est juste une lecture!

Si X a des sous-classes chargées, quelques lectures supplémentaires sont nécessaires; ils sont probablement co-localisés de sorte que le coût supplémentaire est également très faible.

Bonnes nouvelles tout le monde!

8
irreputable

instanceof va probablement coûter plus cher qu'un simple équivalent 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 surchargeant une méthode commune, comme tout manuel de débutant, etc. Demian ci-dessus suggèrent).

Pourquoi donc? Parce que ce qui va probablement arriver, c’est que vous avez plusieurs interfaces, qui fournissent certaines fonctionnalités (disons, les interfaces x, y et z), et des objets à manipuler pouvant implémenter (ou non) l’une de ces interfaces ... mais Pas directement. Dis, par exemple, j'ai:

w s'étend x

Un 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. L'informatique (d instance de x) nécessite de prendre d.getClass (), de parcourir les interfaces implémentées pour savoir si on est == à x, et si ce n'est pas le cas de manière récurrente pour tous leurs ancêtres ... Dans. notre cas, si vous effectuez une première exploration en profondeur de cet arbre, vous obtiendrez 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 sera probablement plus élevée. Dans certains cas, le JIT peut optimiser l'essentiel, 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 manière réaliste, cependant, vous passerez la plupart du temps dans cette traversée d’arbres.

Si cela devient un problème, je suggérerais plutôt d'utiliser une carte de gestionnaire, liant la classe concrète de l'objet à une fermeture assurant le traitement. Il supprime la phase de traversée des arbres en faveur 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 centimes, j'espère qu'ils vous aideront ...

5
Varkhan

instanceof est très efficace, il est donc peu probable que votre performance en souffre ..__ Cependant, l'utilisation de nombreuses instances suggère un problème de conception.

Si vous pouvez utiliser xClass == String.class, c'est plus rapide. Remarque: vous n'avez pas besoin d'instanceof pour les classes finales.

4
Peter Lawrey

Demian et Paul mentionnent un bon point; Cependant , l'emplacement du code à exécuter dépend vraiment de la manière dont vous voulez utiliser les données ...

Je suis un grand fan de petits objets de données pouvant être utilisés de nombreuses manières. Si vous suivez l'approche de substitution (polymorphe), vos objets ne peuvent être utilisés que "à sens unique".

C'est là qu'interviennent les motifs ...

Vous pouvez utiliser une double répartition (comme dans le modèle de visiteur) pour demander à chaque objet de vous "appeler" en se transmettant lui-même - cela résoudra le type de l'objet. Cependant (encore), vous aurez besoin d'une classe capable de "faire des choses" avec tous les sous-types possibles.

Je préfère utiliser un modèle de stratégie dans lequel 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 exactes, mais présente l'avantage d'être extensible - les contributeurs tiers peuvent ajouter leurs propres types et gestionnaires. (C'est bien 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);
    }
}
4
Scott Stanchfield

'instanceof' est en fait un opérateur, tel que + ou -, et je pense qu'il a sa propre instruction de bytecode JVM. Cela devrait être beaucoup 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, votre conception devra peut-être être retravaillée. Pensez à intégrer le comportement spécifique à une sous-classe dans les sous-classes elles-mêmes.

4
Outlaw Programmer

Instanceof est très rapide. Cela revient à un bytecode utilisé pour la comparaison de référence de classe. Essayez quelques millions d'instances dans une boucle et voyez par vous-même.

4
Apocalisp

Il est difficile de dire comment une machine virtuelle virtuelle implémente une instance, mais dans la plupart des cas, les objets sont comparables aux structures et les classes le sont également, et chaque structure d'objet a un pointeur sur la structure de classe dont il est une instance. Donc, en fait, instanceof pour

if (o instanceof Java.lang.String)

pourrait ê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 et de le comparer à un autre pointeur (ce qui revient fondamentalement à tester à un nombre égal à 32 bits), je dirais que l’opération peut être très rapide.

Cela ne doit pas forcément dépendre de la JVM. Toutefois, si cela devait s'avérer être l'opération goulet d'étranglement dans votre code, la mise en œuvre de la machine virtuelle Java serait plutôt médiocre. Même celui qui n'a pas de compilateur JIT et interprète uniquement du code devrait pouvoir créer une instance de test en un rien de temps.

3
Mecki

InstanceOf est un avertissement de mauvaise conception orientée objet.

Les machines virtuelles actuelles signifient que instanceOf ne constitue pas en soi un problème de performances. Si vous vous trouvez souvent en train de l’utiliser, en particulier pour les fonctionnalités principales, il est probablement temps de regarder le design. Les gains de performance (et de simplicité/facilité de maintenance) du refactoring pour une meilleure conception l'emporteront largement sur les cycles de processeur réellement utilisés pour l'appel instanceOf.

Donner un très petit exemple de programmation simpliste.

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

Une architecture médiocre aurait-elle été préférable si SomeObject était la classe parente de deux classes enfant, chaque classe enfant remplaçant une méthode (doSomething) afin que le code se présente comme tel:

Someobject.doSomething();
3
Demian Krige

Je reviendrai vers vous sur l'exemple de performance. Mais un moyen d'éviter les problèmes (ou leur 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 all les méthodes des sous-classes pour lesquelles vous devez faire instanceof. Lorsqu'une méthode ne s'applique pas à une sous-classe spécifique, fournissez simplement une implémentation fictive de cette méthode. Si je ne comprenais pas bien le problème, c’est ainsi que j’ai abordé le problème par le passé. 

3
Jose Quijada

Généralement, la raison pour laquelle l’opérateur "instanceof" est mal vu dans un cas comme celui-ci (où instanceof vérifie les sous-classes de cette classe de base) est que vous devez déplacer les opérations dans une méthode et les remplacer par 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();

et puis avoir l'implémentation de "doEverything ()" dans Class1, appelez "doThis ()", et dans Class2, appelez "doThat ()", et ainsi de suite.

2
Paul Tomblin

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

if(a instanceof AnyObject){
}

est plus rapide que:

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

Une autre chose est si vous avez besoin de mettre en cascade plusieurs instances de. Ensuite, un commutateur qui appelle une seule fois getType () est plus rapide.

2
Horcrux7

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 à d’autres tests. Cependant, 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();
}
}
1
salexinx

Si la vitesse est votre seul objectif, alors utiliser des constantes int pour identifier des sous-classes semble nous permettre de gagner du temps en millisecondes.

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

terrible OO design, mais si votre analyse des performances indique que c’est là que se situe le goulot d’étranglement, alors peut-être. Dans mon code, le code d'envoi prend 10% du temps total d'exécution, ce qui peut contribuer à une amélioration de 1% de la vitesse totale.

1
Salix alba

Je préfère également une approche enum, mais j'utiliserais une classe de base abstraite pour forcer les sous-classes à implémenter la méthode getType().

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

J'ai pensé qu'il serait peut-être intéressant de soumettre un contre-exemple au consensus général sur cette page, selon lequel "instance de" n'est pas assez cher pour s'inquiéter. J'ai trouvé que j'avais du code dans une boucle interne qui (dans une tentative historique d'optimisation) a

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

où l'appel de head () sur un SingleItem retourne la valeur inchangée. Remplacer le code par

seq = seq.head();

me donne une accélération de 269 ms à 169 ms, malgré le fait que des choses assez lourdes se produisent dans la boucle, comme la conversion chaîne à double. Il est possible que l'accélération soit plus due à l'élimination de la branche conditionnelle qu'à celle de l'instance de l'opérateur lui-même. mais j'ai pensé qu'il valait la peine de le mentionner. 

0
Michael Kay

En ce qui concerne la note de Peter Lawrey, vous n'avez pas besoin d'instanceof pour les classes finales et vous pouvez simplement utiliser une égalité de référence, soyez prudent! Même si les classes finales ne peuvent pas être étendues, leur chargement par le même chargeur de classes n'est pas garanti. Utilisez uniquement x.getClass () == SomeFinal.class ou son équivalent si vous êtes absolument certain qu’un seul chargeur de classe est en jeu pour cette section de code.

0
Miles Elam

Vous devez mesurer/profil si c'est vraiment un problème de performance dans votre projet. Si c'est le cas, je recommanderais une refonte, si possible. Je suis sûr que vous ne pouvez pas battre l'implémentation native de la plate-forme (é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. Vous pourriez peut-être utiliser un magasin associatif, par exemple. a Map <Class, Object> si vous n'êtes intéressé que par les types concrets.

0
Karl