Falando em termos de desempenho:
TL; DR
Use Instância ou instância com desempenho semelhante. isAssignableFrom é um pouco mais lento.
Ordenado por desempenho:
- isInstance
- instanceof (+ 0,5%)
- isAssignableFrom (+ 2,7%)
Com base em uma referência de 2000 iterações no JAVA 8 Windows x64, com 20 iterações de aquecimento.
Em teoria
Usando um visualizador de código de byte suave , podemos converter cada operador em bytecode.
No contexto de:
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);
Medindo quantas instruções de código de byte são usadas por cada operador, podemos esperar que instanceof e isInstance sejam mais rápidos que isAssignableFrom . No entanto, o desempenho real NÃO é determinado pelo bytecode, mas pelo código da máquina (que depende da plataforma). Vamos fazer um micro benchmark para cada um dos operadores.
A referência
Crédito: Conforme recomendado por @ aleksandr-dubinsky, e obrigado a @yura por fornecer o código base, aqui está uma referência da JMH (consulte este guia de ajuste ):
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();
}
}
Forneceu os seguintes resultados (a pontuação é um número de operações em uma unidade de tempo ; portanto, quanto maior a pontuação, melhor):
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
Atenção
- o benchmark depende da JVM e da plataforma. Como não há diferenças significativas entre cada operação, pode ser possível obter um resultado diferente (e talvez uma ordem diferente!) Em uma versão JAVA diferente e / ou plataformas como Solaris, Mac ou Linux.
- o benchmark compara o desempenho de "é B uma instância de A" quando "B estende A" diretamente. Se a hierarquia de classes for mais profunda e complexa (como B estende X, que estende Y, que estende Z, que estende A), os resultados podem ser diferentes.
- geralmente é recomendável escrever o código primeiro escolhendo um dos operadores (o mais conveniente) e depois criar um perfil do código para verificar se há um gargalo de desempenho. Talvez esse operador seja insignificante no contexto do seu código ou talvez ...
- em relação ao ponto anterior,
instanceof
no contexto do seu código pode ser otimizado mais facilmente do que um isInstance
por exemplo ...
Para dar um exemplo, faça o seguinte loop:
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;
Graças ao JIT, o código é otimizado em algum momento e obtemos:
- instanceof: 6ms
- isInstance: 12ms
- isAssignableFrom: 15ms
Nota
Originalmente, este post estava fazendo seu próprio benchmark usando um loop for em JAVA bruto, o que forneceu resultados não confiáveis, pois algumas otimizações como Just In Time podem eliminar o loop. Portanto, ele media principalmente quanto tempo o compilador JIT levou para otimizar o loop: consulte Teste de desempenho independente do número de iterações para obter mais detalhes
Perguntas relacionadas