In Bezug auf die Leistung:
TL;DR
Verwenden Sie isInstance ou instanceof die eine ähnliche Leistung aufweisen. isAssignableFrom ist etwas langsamer.
Sortiert nach Leistung:
- isInstance
- instanceof (+ 0.5%)
- isAssignableFrom (+ 2.7%)
Basierend auf einem Benchmark von 2000 Iterationen auf JAVA 8 Windows x64, mit 20 Aufwärmiterationen.
Theoretisch
Mit einem weichen wie Bytecode-Betrachter können wir jeden Operator in Bytecode übersetzen.
Im Kontext von:
package foo;
public class Benchmark
{
public static final Object a = new A();
public static final Object b = new B();
...
}
JAVA:
b instanceof A;
Bytecode:
getstatic foo/Benchmark.b:java.lang.Object
instanceof foo/A
JAVA:
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);
JAVA:
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);
Wenn man misst, wie viele Bytecode-Anweisungen von jedem Operator verwendet werden, könnte man erwarten instanceof y isInstance schneller zu sein als isAssignableFrom . Die tatsächliche Leistung wird jedoch NICHT durch den Bytecode bestimmt, sondern durch den Maschinencode (der plattformabhängig ist). Führen wir für jeden der Operatoren einen Mikro-Benchmark durch.
Die Benchmark
Credit: Wie von @aleksandr-dubinsky empfohlen und mit Dank an @yura für die Bereitstellung des Basiscodes, ist hier ein JMH Benchmark (siehe diese Tuning-Leitfaden ):
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();
}
}
Ergab die folgenden Ergebnisse (die Punktzahl ist eine Anzahl von Vorgängen in einer Zeiteinheit je höher also die Punktzahl, desto besser):
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
Warnung
- der Benchmark ist JVM- und plattformabhängig. Da es keine signifikanten Unterschiede zwischen den einzelnen Operationen gibt, kann es möglich sein, ein anderes Ergebnis (und vielleicht eine andere Reihenfolge!) auf einer anderen JAVA-Version und/oder Plattform wie Solaris, Mac oder Linux zu erhalten.
- der Benchmark vergleicht die Leistung von "ist B eine Instanz von A", wenn "B erweitert A" direkt. Wenn die Klassenhierarchie tiefer und komplexer ist (z. B. B erweitert X, das Y erweitert, das Z erweitert, das A erweitert), könnten die Ergebnisse anders ausfallen.
- ist es in der Regel ratsam, den Code zuerst zu schreiben und dabei einen der Operatoren (den bequemsten) zu wählen und dann ein Profil zu erstellen, um zu prüfen, ob es einen Leistungsengpass gibt. Vielleicht ist dieser Operator im Zusammenhang mit Ihrem Code vernachlässigbar, oder vielleicht...
- in Bezug auf den vorherigen Punkt,
instanceof
im Kontext Ihres Codes möglicherweise leichter optimiert werden als eine isInstance
zum Beispiel...
Als Beispiel sei die folgende Schleife genannt:
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;
Dank JIT wird der Code an einem bestimmten Punkt optimiert und wir erhalten:
- instanceof: 6ms
- isInstance: 12ms
- isAssignableFrom : 15ms
Hinweis
Ursprünglich wurde in diesem Beitrag ein eigener Benchmark mit einer für Schleife in rohem JAVA, was zu unzuverlässigen Ergebnissen führte, da eine Optimierung wie Just In Time die Schleife eliminieren kann. Es wurde also hauptsächlich gemessen, wie lange der JIT-Compiler für die Optimierung der Schleife brauchte: siehe Leistungstest unabhängig von der Anzahl der Iterationen für weitere Einzelheiten
Verwandte Fragen
17 Stimmen
F t )
2 Stimmen
Téô T '