Mówienie w kategoriach wydajności:
TL; DR
Użyj isInstance lub instanceof, które mają podobną wydajność. isAssignableFrom jest nieco wolniejszy.
Posortowane według wydajności:
- isInstance
- instanceof (+ 0,5%)
- isAssignableFrom (+ 2,7%)
Oparty na teście 2000 iteracji na JAVA 8 Windows x64, z 20 iteracjami rozgrzewania.
W teorii
Korzystając z miękkiej przeglądarki kodu bajtowego , możemy przetłumaczyć każdego operatora na kod bajtowy.
W kontekście:
package foo;
public class Benchmark
{
public static final Object a = new A();
public static final Object b = new B();
...
}
JAWA:
b instanceof A;
Kod bajtowy:
getstatic foo/Benchmark.b:java.lang.Object
instanceof foo/A
JAWA:
A.class.isInstance(b);
Kod bajtowy:
ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Class isInstance((Ljava/lang/Object;)Z);
JAWA:
A.class.isAssignableFrom(b.getClass());
Kod bajtowy:
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);
Mierząc liczbę instrukcji kodu bajtowego używanych przez każdego operatora, można oczekiwać, że instanceof i isInstance będą szybsze niż isAssignableFrom . Rzeczywista wydajność NIE jest jednak określana przez kod bajtowy, ale przez kod maszynowy (zależny od platformy). Zróbmy mikro test porównawczy dla każdego operatora.
Benchmark
Credit: Zgodnie z zaleceniami @ aleksandr-dubinsky i dzięki @yura za dostarczenie kodu podstawowego, oto test porównawczy JMH (zobacz ten przewodnik strojenia ):
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();
}
}
Dał następujące wyniki (wynik to liczba operacji w jednostce czasu , więc im wyższy wynik, tym lepiej):
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
Ostrzeżenie
- Benchmark jest zależny od JVM i platformy. Ponieważ nie ma znaczących różnic między każdą operacją, może być możliwe uzyskanie innego wyniku (a może innej kolejności!) Na innej wersji JAVA i / lub platformach takich jak Solaris, Mac lub Linux.
- test porównawczy porównuje działanie „jest B, instancja A”, gdy „B rozszerza A” bezpośrednio. Jeśli hierarchia klas jest głębsza i bardziej złożona (jak B rozszerza X, który rozszerza Y, który rozszerza Z, który rozszerza A), wyniki mogą być inne.
- zwykle zaleca się, aby najpierw napisać kod, wybierając jednego z operatorów (najwygodniejszy), a następnie profilować kod, aby sprawdzić, czy występuje wąskie gardło wydajności. Może ten operator jest nieistotny w kontekście twojego kodu, a może ...
- w odniesieniu do poprzedniego punktu,
instanceof
w kontekście twojego kodu może zostać zoptymalizowany łatwiej niż isInstance
na przykład ...
Aby podać przykład, wykonaj następującą pętlę:
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;
Dzięki JIT kod jest w pewnym momencie optymalizowany i otrzymujemy:
- instanceof: 6ms
- isInstance: 12ms
- isAssignableFrom: 15ms
Uwaga
Pierwotnie ten post przeprowadzał własny test porównawczy przy użyciu pętli for w surowej JAVA, co dawało niewiarygodne wyniki, ponieważ niektóre optymalizacje, takie jak Just In Time, mogą wyeliminować pętlę. Więc głównie mierzyło to, ile czasu zajął kompilatorowi JIT, aby zoptymalizować pętlę: zobacz Test wydajności niezależny od liczby iteracji, aby uzyskać więcej szczegółów
Powiązane pytania