L'impatto sulle prestazioni dell'utilizzo di instanceof in Java


314

Sto lavorando su un'applicazione e un approccio progettuale prevede un uso estremamente pesante instanceofdell'operatore. Mentre so che il design OO generalmente cerca di evitare l'uso instanceof, questa è una storia diversa e questa domanda è puramente correlata alle prestazioni. Mi chiedevo se ci fosse qualche impatto sulle prestazioni? È veloce come ==?

Ad esempio, ho una classe base con 10 sottoclassi. In una singola funzione che accetta la classe base, controllo se la classe è un'istanza della sottoclasse ed eseguo alcune routine.

Uno degli altri modi in cui ho pensato di risolverlo è stato invece quello di utilizzare una primitiva intera "id di tipo" e utilizzare una maschera di bit per rappresentare le categorie delle sottoclassi, quindi fare semplicemente un confronto maschera di bit delle sottoclassi "tipo id" a un maschera costante che rappresenta la categoria.

La instanceofJVM è in qualche modo ottimizzata per essere più veloce di così? Voglio attenermi a Java, ma le prestazioni dell'app sono fondamentali. Sarebbe bello se qualcuno che è stato su questa strada prima potesse offrire qualche consiglio. Sto pignolando troppo o mi sto concentrando sulla cosa sbagliata da ottimizzare?


81
Penso che il punto della domanda, tuttavia, sia stato quello di mettere da parte la questione delle migliori pratiche OO ed esaminare le prestazioni.
Dave L.,

3
@Dave L. Normalmente concordo, ma l'OP menziona che sta cercando alcune tecniche di ottimizzazione generale e non è sicuro che il suo problema sia correlato a "instanceof". Penso che valga la pena menzionare almeno il design "corretto" in modo che possa profilare entrambe le scelte.
Programmatore fuorilegge,

51
Ugh ... Perché tutte le risposte mancano il punto della domanda e forniscono la stessa vecchia retorica Knuth sull'ottimizzazione? La tua domanda è se instanceof è significativamente / sorprendentemente più lento rispetto al controllo dell'oggetto class con ==, e ho scoperto che non lo è.
paffuto

3
Le prestazioni di instanceof e casting sono abbastanza buone. Ho pubblicato alcune temporizzazione in Java7 intorno differenti approcci al problema qui: stackoverflow.com/questions/16320014/...
Wheezil

Risposte:


268

I moderni compilatori JVM / JIC hanno rimosso l'hit di prestazioni della maggior parte delle operazioni tradizionalmente "lente", tra cui istanza, gestione delle eccezioni, riflessione, ecc.

Come scrisse Donald Knuth, "Dovremmo dimenticare le piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali". Le prestazioni di instanceof probabilmente non saranno un problema, quindi non perdere tempo a trovare soluzioni esotiche fino a quando non sei sicuro che sia il problema.


13
Modern JVM / JIC .. Potresti per favore menzionare da quale versione di Java sono state trattate queste ottimizzazioni?
Ravisha,

138
C'è sempre qualcuno che cita Knuth quando le prestazioni sono l'argomento ... Dimenticando che anche Knuth ha affermato (nello stesso articolo) "Nelle discipline ingegneristiche consolidate un miglioramento del 12%, facilmente ottenibile, non è mai considerato marginale e credo che lo stesso punto di vista dovrebbe prevalere nell'ingegneria del software ", quasi tutto il suo lavoro riguardava l'efficienza degli algoritmi e ha scritto algoritmi in assembly per (tra l'altro) ottenere prestazioni migliori. Meh ...
kgadek,

4
A parte qui, ma sarebbe try { ObjT o = (ObjT)object } catch (e) { no not one of these }più veloce più lento ??
Peter,

35
Se "object" è un'istanza di ObjT, il casting è un po 'più veloce rispetto a un'istanza di, ma la differenza rilevata dal mio test rapido è stata di 10-20 ms su 10.000.000 di iterazioni. Se "object" non è un ObjT, tuttavia, la rilevazione dell'eccezione è stata più lenta di 3000 volte - oltre 31.000ms contro ~ 10ms per l'istanza diof.
Steve,

19
un argomento così forte senza alcun "riferimento", è completamente inutile perché solo supponente.
marcorossi,

279

Approccio

Ho scritto un programma di riferimento per valutare diverse implementazioni:

  1. instanceof implementazione (come riferimento)
  2. oggetto orientato tramite una classe astratta e @Override un metodo di prova
  3. usando un'implementazione del proprio tipo
  4. getClass() == _.class implementazione

Ho usato jmh per eseguire il benchmark con 100 chiamate di riscaldamento, 1000 iterazioni in fase di misurazione e con 10 fork. Quindi ogni opzione è stata misurata con 10.000 volte, il che richiede 12:18:57 per eseguire l'intero benchmark sul mio MacBook Pro con macOS 10.12.4 e Java 1.8. Il benchmark misura il tempo medio di ciascuna opzione. Per maggiori dettagli vedi la mia implementazione su GitHub .

Per completezza: esiste una versione precedente di questa risposta e il mio punto di riferimento .

risultati

| Operazione | Durata in nanosecondi per operazione | Relativo a instanceof |
| ------------ | ------------------------------------ - | ------------------------ |
| INSTANCEOF | 39.598 ± 0,022 ns / op | 100,00% |
| GETCLASS | 39.687 ± 0,021 ns / op | 100,22% |
| TIPO | 46.295 ± 0,026 ns / op | 116,91% |
| OO | 48.078 ± 0,026 ns / op | 121,42% |

tl; dr

In Java 1.8 instanceofè l'approccio più veloce, sebbene getClass()sia molto vicino.


58
+0.(9)per la scienza!

16
+ l'altro 0.1 da me: D
Tobias Reich,

14
@TobiasReich Quindi abbiamo ottenuto +1.0(9). :)
Pavel

9
Non penso che questo abbia qualcosa di significativo. Il codice misura l'utilizzo System.currentTimeMillis()su un'operazione che non è molto più di una singola chiamata di metodo, che dovrebbe dare molto a bassa precisione. Utilizzare invece un framework di riferimento come JMH !
Lii,

6
O semplicemente fare il tempismo dell'intero miliardo di chiamate anziché per chiamata.
LegendLength

74

Ho appena fatto un semplice test per vedere come le prestazioni di instanceOf si confrontano con una semplice chiamata s.equals () a un oggetto stringa con una sola lettera.

in un ciclo di 10.000.000 l'istanza di Of mi ha dato 63-96ms e la stringa è uguale a 106-230ms

Ho usato java jvm 6.

Quindi nel mio semplice test è più veloce fare un'istanza di Invece di un confronto di stringhe di un carattere.

l'utilizzo di .equals () di Integer anziché di string mi ha dato lo stesso risultato, solo quando ho usato == sono stato più veloce di istanza di 20 ms (in un ciclo di 10.000.000)


4
Sarebbe possibile pubblicare il codice qui? Sarebbe fantastico!
The Alchemist,

7
Come si è confrontato istanza di confronto con la funzione polimorfica?
Chris,

21
Perché confronti istanza di un String.equals ()? Se vuoi controllare il tipo devi object.getClass (). Equals (SomeType.class)
marsbear,

4
@marsbear equals()non lo taglierà, perché la sottoclasse; hai bisogno isAssignableFrom().
David Moles,

1
@marsbear Giusto, ma non è un test migliore di ciò che l'OP stava chiedendo.
David Moles,

20

Gli elementi che determineranno l'impatto sulle prestazioni sono:

  1. Il numero di classi possibili per le quali l'operatore instanceof potrebbe restituire true
  2. La distribuzione dei dati: la maggior parte delle operazioni di instanceof sono state risolte nel primo o nel secondo tentativo? Ti consigliamo di mettere il più probabile per restituire prima le operazioni vere.
  3. L'ambiente di distribuzione. L'esecuzione su una macchina virtuale Sun Solaris è significativamente diversa dalla JVM Windows di Sun. Solaris verrà eseguito in modalità "server" per impostazione predefinita, mentre Windows verrà eseguito in modalità client. Le ottimizzazioni JIT su Solaris consentiranno a tutti gli accessi ai metodi di fare lo stesso.

Ho creato un microbenchmark per quattro diversi metodi di spedizione . I risultati di Solaris sono i seguenti, con il numero più piccolo più veloce:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

18

Rispondere alla tua ultima domanda: a meno che un profiler non ti dica, che passi una quantità ridicola di tempo in un'istanza di: Sì, stai puntando.

Prima di chiederti come ottimizzare qualcosa che non ha mai avuto bisogno di essere ottimizzato: scrivi il tuo algoritmo nel modo più leggibile ed eseguilo. Eseguilo, fino a quando il compilatore jit non ha la possibilità di ottimizzarlo da solo. Se poi hai problemi con questo pezzo di codice, usa un profiler per dirti dove ottenere di più e ottimizzarlo.

In tempi di compilatori altamente ottimizzati, è probabile che le tue ipotesi sui colli di bottiglia siano completamente sbagliate.

E nel vero spirito di questa risposta (di cui credo sinceramente): non so assolutamente come si collegano instanceof e == una volta che il compilatore jit ha avuto la possibilità di ottimizzarlo.

Ho dimenticato: non misurare mai la prima corsa.


1
Ma il poster originale menzionato le prestazioni sono state fondamentali per questa applicazione, quindi non è irragionevole ottimizzare presto in quella situazione. In altre parole, non scriveresti un gioco 3d in GWBasic e alla fine diciamo, ok iniziamo a ottimizzare questo, il primo passo è portarlo su c ++.
LegendLength

GWBasic potrebbe essere un ottimo inizio per i giochi 3d, se sono disponibili librerie appropriate. Ma a parte questo (poiché è un argomento artificiale): OP non sta chiedendo una riscrittura completa come ottimizzazione. Si tratta di un singolo costrutto in cui non sappiamo nemmeno se l'impatto è significativo (anche se esiste un modo con prestazioni migliori per fare lo stesso nella versione corrente del compilatore ). Sono fermamente in piedi dietro c2.com/cgi/wiki?ProfileBeforeOptimizing e la mia risposta. L'ottimizzazione preliminare è la radice di tutti i mali! Rende più difficile la manutenzione - e la manutenzione è l'aspetto che vale la pena ottimizzare
Olaf Kock

15

Ho la stessa domanda, ma poiché non ho trovato "metriche delle prestazioni" per un caso d'uso simile al mio, ho creato un altro codice di esempio. Sul mio hardware e Java 6 e 7, la differenza tra instanceof e accendere iterazioni da 10 mln è

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

Quindi, instanceof è molto più lento, specialmente su un numero enorme di istruzioni if-else-if, tuttavia la differenza sarà trascurabile all'interno dell'applicazione reale.

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

Quale risultato è stato Java 6 e quale Java 7? Hai rivisitato questo sotto Java 8? Più significativamente qui, stai confrontando una lunghezza se di instanceofs con ciò che è essenziale un'istruzione case su ints. Penso che ci aspetteremmo che un interruttore int si alleggerisca rapidamente.
Azeroth2b,

1
Non ricordo esattamente cosa stesse succedendo 5 anni fa - penso che sia Java 6 che Java 7 abbiano avuto risultati simili, ecco perché c'è un solo risultato fornito (a condizione che 2 righe siano per gerarchia di profondità di classe diversa) ... e no , non ho provato a confrontarlo con Java 8. Viene fornito l'intero codice del test - è possibile copiarlo / incollarlo e verificare gli ambienti necessari (nota - al giorno d'oggi userei il test JMH per questo).
Xtra Coder,

9

instanceof è veramente veloce, prendendo solo alcune istruzioni della CPU.

Apparentemente, se una classe Xnon ha sottoclassi caricate (JVM lo sa), instanceofpuò essere ottimizzata come:

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

Il costo principale è solo una lettura!

Se Xsono state caricate sottoclassi, sono necessarie alcune letture in più; sono probabilmente situati in una posizione, quindi anche il costo aggiuntivo è molto basso.

Buone notizie a tutti!


2
può essere ottimizzato o è ottimizzato? fonte?

@vaxquis can come jvm impl specific
RecursiveExceptionException

@itzJanuary sospiro ti sei perso il punto della mia domanda qui: tutti sanno che il compilatore può ottimizzare foo- ma è fooin realtà attualmente ottimizzato per Oracle javac / VM - o è solo possibile che farò in futuro? Inoltre, ho chiesto al risponditore che ha qualche fonte di supporto (sia esso documenti, codice sorgente, blog di sviluppo) che documenta che può davvero essere ottimizzato o ottimizzato ? Senza di essa, questa risposta è solo una riflessione casuale su ciò che il compilatore può eventualmente fare.

@vaxquis Non hai mai menzionato l'Hotspot VM ma in quel caso non so se sia "ottimizzato".
RecursiveExceptionException il

1
Recentemente letto che JIT (JVM 8) ottimizzerà un sito di chiamata per 1 o 2 tipi mediante chiamate dirette, ma ritorna alla vtable se si incontrano più di due tipi effettivi. Quindi, solo avere due tipi concreti che passano attraverso un sito di chiamata in fase di esecuzione rappresenta un vantaggio in termini di prestazioni.
simon.watts

5

Instanceof è molto veloce. Si riduce a un bytecode utilizzato per il confronto dei riferimenti di classe. Prova alcuni milioni di istanze in un ciclo e vedi di persona.


5

instanceof sarà probabilmente più costoso di un semplice uguale nella maggior parte delle implementazioni del mondo reale (vale a dire, quelle in cui è realmente necessaria instanceof, e non puoi semplicemente risolverlo sovrascrivendo un metodo comune, come ogni libro di testo per principianti e Demian sopra suggerisce).

Perché? Perché ciò che probabilmente accadrà è che hai diverse interfacce, che forniscono alcune funzionalità (diciamo, interfacce x, ye z) e alcuni oggetti da manipolare che possono (o meno) implementare una di quelle interfacce ... ma non direttamente. Ad esempio, ho:

w estende x

A implementa w

B estende A

C estende B, implementa y

D estende C, implementa z

Supponiamo che stia elaborando un'istanza di D, l'oggetto d. Il calcolo (d istanza di x) richiede di prendere d.getClass (), scorrere le interfacce implementate per sapere se si è ==x, e in caso contrario ricorrere in modo ricorsivo per tutti i loro antenati ... Nel nostro caso, se fai una prima esplorazione ampia di quell'albero, produce almeno 8 confronti, supponendo che y e z non estendano nulla ...

La complessità di un albero di derivazione del mondo reale è probabilmente maggiore. In alcuni casi, JIT può ottimizzare la maggior parte di esso, se è in grado di risolvere in anticipo d come essere, in tutti i casi possibili, un'istanza di qualcosa che estende x. Realisticamente, tuttavia, attraverserai quell'albero per la maggior parte del tempo.

Se questo diventa un problema, suggerirei invece di utilizzare una mappa del gestore, collegando la classe concreta dell'oggetto a una chiusura che esegue la gestione. Rimuove la fase di attraversamento dell'albero a favore di una mappatura diretta. Tuttavia, fai attenzione che se hai impostato un gestore per C.class, il mio oggetto d sopra non verrà riconosciuto.

ecco i miei 2 centesimi, spero che mi aiutino ...


5

instanceof è molto efficiente, quindi è improbabile che la tua performance ne risenta. Tuttavia, l'uso di un sacco di esempio indica un problema di progettazione.

Se puoi usare xClass == String.class, questo è più veloce. Nota: non hai bisogno di instanceof per le classi finali.


1
A proposito, cosa intendi con "non ho bisogno di instanceof per le classi finali"?
Pacerier

Un'ultima classe non può avere sottoclassi. In questo caso x.getClass() == Class.classè lo stesso dix instanceof Class
Peter Lawrey

Fantastico, supponendo che x non sia nullo, quale preferiresti?
Pacerier

Buon punto. Dipenderebbe se mi aspetto xdi essere nullsuppongo. (O qualunque sia più chiaro)
Peter Lawrey

Hmm ho appena capito che potremmo usare anche java.lang.class.isAssignableFrom, sei consapevole se l' istanza della parola chiave usa internamente funzioni come queste?
Pacerier

4

Generalmente il motivo per cui l'operatore "instanceof" è malvisto in un caso del genere (in cui l'istanza sta verificando la presenza di sottoclassi di questa classe di base) è perché ciò che si dovrebbe fare è spostare le operazioni in un metodo e sostituirlo con l'appropriato sottoclassi. Ad esempio, se hai:

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

Puoi sostituirlo con

o.doEverything();

e quindi avere l'implementazione di "doEverything ()" nella chiamata Class1 "doThis ()", e nella chiamata Class2 "doThat ()" e così via.


11
Ma a volte non puoi. Se stai implementando un'interfaccia che ti sta prendendo in un oggetto e devi dire di che tipo è, allora instanceof è davvero l'unica opzione. Potresti provare a trasmettere, ma instanceof è generalmente più pulito.
Herms,

4

'instanceof' è in realtà un operatore, come + o -, e credo che abbia le sue istruzioni bytecode JVM. Dovrebbe essere molto veloce.

Non dovrei che se hai un interruttore in cui stai testando se un oggetto è un'istanza di qualche sottoclasse, potrebbe essere necessario rielaborare il tuo progetto. Prendi in considerazione l'idea di inserire il comportamento specifico della sottoclasse nelle sottoclassi stesse.


4

Demian e Paul menzionano un buon punto; tuttavia , il posizionamento del codice da eseguire dipende davvero da come si desidera utilizzare i dati ...

Sono un grande fan dei piccoli oggetti dati che possono essere utilizzati in molti modi. Se segui l'approccio di override (polimorfico), i tuoi oggetti possono essere usati solo "a senso unico".

È qui che entrano in gioco gli schemi ...

Puoi usare il doppio invio (come nel modello visitatore) per chiedere a ciascun oggetto di "chiamarti" passando da solo - questo risolverà il tipo di oggetto. Tuttavia (di nuovo) avrai bisogno di una classe in grado di "fare cose" con tutti i possibili sottotipi.

Preferisco usare un modello di strategia, in cui è possibile registrare strategie per ogni sottotipo che si desidera gestire. Qualcosa di simile al seguente. Nota che questo aiuta solo per corrispondenze esatte di tipi, ma ha il vantaggio di essere estensibile: i collaboratori di terze parti possono aggiungere i propri tipi e gestori. (Questo è utile per framework dinamici come OSGi, in cui è possibile aggiungere nuovi bundle)

Spero che questo ispirerà alcune altre idee ...

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

Scrivo un test delle prestazioni basato su archetipo jmh-java-benchmark: 2.21. JDK è openjdk e la versione è 1.8.0_212. La macchina di prova è mac pro. Il risultato del test è:

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

Il risultato mostra che: getClass è migliore di instanceOf, il che è contrario ad altri test. Tuttavia, non so perché.

Il codice di prova è di seguito:

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

Se dovessi speculare, ciò che fa instanceof è probabilmente più complesso. Un controllo getClass () == eseguirà un controllo 1: 1 preciso, in cui istanza verifica una gerarchia, ad esempio passando un'istanza myHashSet della raccolta, ma myHashSet.getClass () == Collection.class no. In sostanza, non sono operazioni equivalenti, quindi non sono troppo sorpreso dal fatto che anche le prestazioni siano diverse.
AMTerp

3

È difficile dire come una determinata JVM implementa l'istanza di, ma nella maggior parte dei casi, anche gli oggetti sono paragonabili a strutture e classi e ogni struttura di oggetto ha un puntatore alla struttura di classe di cui è un'istanza. Quindi in realtà instanceof per

if (o instanceof java.lang.String)

potrebbe essere veloce come il seguente codice C.

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

supponendo che sia stato creato un compilatore JIT e svolga un lavoro dignitoso.

Considerando che questo sta solo accedendo a un puntatore, ottenendo un puntatore a un certo offset a cui punta il puntatore e confrontandolo con un altro puntatore (che è sostanzialmente lo stesso del test con numeri a 32 bit uguali), direi che l'operazione può effettivamente sii molto veloce.

Non deve, tuttavia, dipende molto dalla JVM. Tuttavia, se ciò si rivelasse l'operazione di collo di bottiglia nel tuo codice, considererei l'implementazione di JVM piuttosto scadente. Anche uno che non ha un compilatore JIT e interpreta solo il codice dovrebbe essere in grado di eseguire un test di istanza praticamente in pochissimo tempo.


1
Non deve capire se eredita da java.lang.String?
WW.

1
Ecco perché ho detto che "potrebbe" essere veloce. In realtà esegue un ciclo, controllando prima iAmInstanceOf rispetto alla classe in questione, quindi sale verso l'alto l'albero ereditario di o e ripetendo questo controllo per ogni superclasse di o (quindi potrebbe essere necessario eseguire questo ciclo un paio di volte per una partita)
Mecki,

3

Ti richiamerò su istanza di performance. Ma un modo per evitare del tutto il problema (o la sua mancanza) sarebbe quello di creare un'interfaccia genitore per tutte le sottoclassi su cui è necessario eseguire istanza di. L'interfaccia sarà un super set di tutti i metodi nelle sottoclassi per le quali è necessario eseguire il controllo dell'istanza. Laddove un metodo non si applica a una sottoclasse specifica, è sufficiente fornire un'implementazione fittizia di questo metodo. Se non ho frainteso il problema, è così che ho risolto il problema in passato.


2

InstanceOf è un avvertimento di scarsa progettazione orientata agli oggetti.

Le JVM attuali significano che l' istanza di non è una preoccupazione per le prestazioni in sé. Se ti ritrovi a usarlo molto, soprattutto per le funzionalità di base, è probabilmente il momento di esaminare il design. Le prestazioni (e la semplicità / manutenibilità) ottenute dal refactoring per una progettazione migliore supereranno di gran lunga qualsiasi ciclo effettivo del processore impiegato per l' istanza effettiva della chiamata.

Per fornire un esempio di programmazione semplicissimo.

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

È un'architettura scadente una scelta migliore sarebbe stata quella di avere SomeObject come classe padre di due classi figlio in cui ogni classe figlio sovrascrive un metodo (doSomething) in modo che il codice appaia come tale:

Someobject.doSomething();

61
Ne sono consapevole. Non era quello che ho chiesto.
Josh,

Non sono sicuro di votare o no perché è un buon punto, ma non risponde alla domanda posta ...
jklp

7
Penso che l'esempio di codice sia in realtà un pessimo: non è possibile estendere la classe Double e inoltre non è possibile derivare Double da un'altra classe. Se avessi usato altre classi per l'esempio, sarebbe stato ok.
Lena Schimmel,

6
Inoltre, se le classi figlio di SomeObject sono oggetti valore, non si desidera inserire la logica in essi. Ad esempio, Pie and Roast potrebbe non essere il posto corretto per la logica putInOven () e putInMouth ().
sk.

la torta e l'arrosto
cucinati da soli

2

Nella moderna versione Java l'operatore instanceof è più veloce come una semplice chiamata di metodo. Questo significa:

if(a instanceof AnyObject){
}

è più veloce come:

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

Un'altra cosa è se hai bisogno di mettere in cascata molte istanze di. Quindi uno switch che chiama solo una volta getType () è più veloce.


1

Se la velocità è il tuo unico obiettivo, l'uso delle costanti int per identificare le sottoclassi sembra radere un millisecondo del tempo

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

design OO terribile, ma se l'analisi delle prestazioni indica che questo è il punto in cui si trova il collo di bottiglia, forse. Nel mio codice il codice di invio prende il 10% del tempo totale di esecuzione e questo forse ha contribuito a un miglioramento della velocità totale dell'1%.


0

È necessario misurare / profilo se si tratta davvero di un problema di prestazioni nel progetto. Se lo è, consiglierei una riprogettazione, se possibile. Sono abbastanza sicuro che non puoi battere l'implementazione nativa della piattaforma (scritta in C). In questo caso dovresti anche considerare l'eredità multipla.

Dovresti dire di più sul problema, forse potresti usare un negozio associativo, ad esempio una mappa <classe, oggetto> se sei interessato solo ai tipi concreti.


0

Per quanto riguarda la nota di Peter Lawrey secondo cui non hai bisogno dell'istanza di classi finali e puoi semplicemente usare un'uguaglianza di riferimento, fai attenzione! Anche se le classi finali non possono essere estese, non è garantito che vengano caricate dallo stesso classloader. Usa x.getClass () == SomeFinal.class o il suo simile solo se sei assolutamente sicuro che ci sia un solo classloader in gioco per quella sezione di codice.


4
Se una classe viene caricata da un caricatore di classi diverso, non credo che neanche l'istanza corrisponderà.
Peter Lawrey,

0

Preferisco anche un approccio enum, ma userei una classe base astratta per forzare le sottoclassi a implementare il getType()metodo.

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

Ho pensato che valesse la pena sottoporre un contro-esempio al consenso generale in questa pagina sul fatto che "instanceof" non è abbastanza costoso da preoccuparsi. Ho scoperto di avere del codice in un ciclo interno che (in un tentativo storico di ottimizzazione) ha fatto

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

dove chiamando head () su un SingleItem restituisce il valore invariato. Sostituzione del codice con

seq = seq.head();

mi dà una velocità da 269ms a 169ms, nonostante ci siano cose abbastanza pesanti nel loop, come la conversione da stringa a doppia. Naturalmente è possibile che l'accelerazione sia più dovuta all'eliminazione del ramo condizionale che all'eliminazione dell'istanza dell'operatore stesso; ma ho pensato che valesse la pena menzionarlo.


Questo potrebbe essere a causa dello ifstesso. Se la distribuzione di trues e falses è quasi pari, l'esecuzione speculativa diventa inutile, il che porta a ritardi significativi.
Dmytro,

-4

Ti stai concentrando sulla cosa sbagliata. La differenza tra instanceof e qualsiasi altro metodo per controllare la stessa cosa probabilmente non sarebbe nemmeno misurabile. Se le prestazioni sono fondamentali, probabilmente Java è la lingua sbagliata. Il motivo principale è che non puoi controllare quando la VM decide di voler raccogliere la spazzatura, il che può portare la CPU al 100% per diversi secondi in un programma di grandi dimensioni (MagicDraw 10 è stato ottimo per questo). A meno che tu non abbia il controllo di tutti i computer su cui verrà eseguito questo programma, non puoi garantire su quale versione di JVM sarà attiva e molti dei più vecchi presentavano importanti problemi di velocità. Se si tratta di una piccola app, potresti essere d'accordo con Java, ma se stai costantemente leggendo e scartando i dati , noterai quando il GC entra in funzione.


7
Ciò è molto meno vero degli algoritmi java garbage collection più moderni di quanto non siano mai stati. Anche gli algoritmi più semplici non si preoccupano più di quanta memoria scarti subito dopo averla utilizzata - si preoccupano solo di quanto viene conservato nelle raccolte di nuova generazione.
Bill Michell,

3
Fantastico, tranne per il fatto che sono sulla JVM più recente e il mio computer esegue ancora la scansione quando il GC è in esecuzione. Su un server ram dual-core da 3 GB. Java non è un linguaggio da utilizzare se le prestazioni contano davvero.
comunicare l'

@David: non è necessario richiedere in tempo reale problemi quando l'app scompare per un certo periodo di tempo. Una divertente che ho riscontrato è un'app Java che si connetteva a uno stream TCP che è morto quando il GC è stato eseguito perché non ha chiuso lo stream per primo e non è stato in grado di gestire il sovraccarico del traffico di rete quando è tornato: sarebbe immediatamente andare in un ciclo in cui viene eseguito GC, quando l'app riprende tenta di sfogliare un mucchio di dati, il che ha fatto esaurire la memoria, che ha attivato il GC, ecc. Java è ottimo per molte attività, ma non attività dove molto sono richieste prestazioni elevate.
contattare il

6
@tloach mi sembra un cattivo design delle app. parli di "performance" come se fosse monodimensionale. Ho lavorato con (e su) molte app Java che erano, ad esempio, performanti nel fornire analisi statistiche interattive scattanti e visualizzazione di set di dati molto grandi, o performanti nell'elaborare volumi di transazioni molto grandi molto rapidamente. "performance" non è solo una cosa, e il fatto che qualcuno possa scrivere un'applicazione che gestisce male la memoria e consente a GC di fare a modo suo non significa che nulla che richieda "performance" dovrebbe essere scritto in qualcos'altro.
David Moles,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.