Parlando in termini di prestazioni:
TL; DR
Utilizzare isInstance o instanceof con prestazioni simili. isAssignableFrom è leggermente più lento.
Ordinati per prestazione:
- isinstance
- istanza di (+ 0,5%)
- isAssignableFrom (+ 2,7%)
Basato su un benchmark di 2000 iterazioni su JAVA 8 Windows x64, con 20 iterazioni di riscaldamento.
In teoria
Usando un visualizzatore di bytecode soft come possiamo tradurre ogni operatore in bytecode.
Nel contesto di:
package foo;
public class Benchmark
{
public static final Object a = new A();
public static final Object b = new B();
...
}
GIAVA:
b instanceof A;
bytecode:
getstatic foo/Benchmark.b:java.lang.Object
instanceof foo/A
GIAVA:
A.class.isInstance(b);
bytecode:
ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Class isInstance((Ljava/lang/Object;)Z);
GIAVA:
A.class.isAssignableFrom(b.getClass());
bytecode:
ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Object getClass(()Ljava/lang/Class;);
invokevirtual java/lang/Class isAssignableFrom((Ljava/lang/Class;)Z);
Misurando quante istruzioni di bytecode sono utilizzate da ciascun operatore, potremmo aspettarci che instanceof e isInstance siano più veloci di isAssignableFrom . Tuttavia, le prestazioni effettive NON sono determinate dal bytecode ma dal codice macchina (che dipende dalla piattaforma). Facciamo un micro benchmark per ciascuno degli operatori.
Il punto di riferimento
Credito: Come consigliato da @ aleksandr-dubinsky, e grazie a @yura per aver fornito il codice di base, ecco un benchmark JMH (vedi questa guida alla messa a punto ):
class A {}
class B extends A {}
public class Benchmark {
public static final Object a = new A();
public static final Object b = new B();
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testInstanceOf()
{
return b instanceof A;
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testIsInstance()
{
return A.class.isInstance(b);
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testIsAssignableFrom()
{
return A.class.isAssignableFrom(b.getClass());
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(TestPerf2.class.getSimpleName())
.warmupIterations(20)
.measurementIterations(2000)
.forks(1)
.build();
new Runner(opt).run();
}
}
Ha ottenuto i seguenti risultati (il punteggio è un numero di operazioni in un'unità di tempo , quindi più è alto il punteggio, meglio è):
Benchmark Mode Cnt Score Error Units
Benchmark.testIsInstance thrpt 2000 373,061 ± 0,115 ops/us
Benchmark.testInstanceOf thrpt 2000 371,047 ± 0,131 ops/us
Benchmark.testIsAssignableFrom thrpt 2000 363,648 ± 0,289 ops/us
avvertimento
- il benchmark è JVM e dipende dalla piattaforma. Poiché non vi sono differenze significative tra ciascuna operazione, potrebbe essere possibile ottenere un risultato diverso (e forse un ordine diverso!) Su una versione JAVA diversa e / o piattaforme come Solaris, Mac o Linux.
- il benchmark confronta le prestazioni di "è B un'istanza di A" quando "B estende A" direttamente. Se la gerarchia di classi è più profonda e più complessa (come B estende X che estende Y che estende Z che estende A), i risultati potrebbero essere diversi.
- di solito si consiglia di scrivere prima il codice selezionando uno degli operatori (il più conveniente) e quindi profilare il codice per verificare se ci sono colli di bottiglia nelle prestazioni. Forse questo operatore è trascurabile nel contesto del tuo codice, o forse ...
- rispetto al punto precedente,
instanceof
nel contesto del codice potrebbe essere ottimizzato più facilmente di un isInstance
esempio ...
Per fare un esempio, prendi il seguente ciclo:
class A{}
class B extends A{}
A b = new B();
boolean execute(){
return A.class.isAssignableFrom(b.getClass());
// return A.class.isInstance(b);
// return b instanceof A;
}
// Warmup the code
for (int i = 0; i < 100; ++i)
execute();
// Time it
int count = 100000;
final long start = System.nanoTime();
for(int i=0; i<count; i++){
execute();
}
final long elapsed = System.nanoTime() - start;
Grazie a JIT, il codice è ottimizzato ad un certo punto e otteniamo:
- istanza di: 6ms
- isInstance: 12ms
- isAssignableFrom: 15ms
Nota
Inizialmente questo post stava facendo il proprio benchmark usando un ciclo for in JAVA non elaborato, che ha dato risultati inaffidabili in quanto alcune ottimizzazioni come Just In Time possono eliminare il ciclo. Quindi è stato soprattutto misurare il tempo impiegato dal compilatore JIT per ottimizzare il loop: vedere Test delle prestazioni indipendentemente dal numero di iterazioni per maggiori dettagli
Domande correlate