Hablando en términos de rendimiento:
TL; DR
Utilice isInstance o instanceof que tienen un rendimiento similar. isAssignableFrom es un poco más lento.
Ordenado por rendimiento:
- isInstance
- instancia de (+ 0.5%)
- isAssignableFrom (+ 2.7%)
Basado en un punto de referencia de 2000 iteraciones en JAVA 8 Windows x64, con 20 iteraciones de calentamiento.
En teoria
Usando un visor de código de bytes suave , podemos traducir cada operador en código de bytes.
En el 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);
Al medir cuántas instrucciones de bytecode utiliza cada operador, podríamos esperar que instancia de e isInstance sea más rápido que isAssignableFrom . Sin embargo, el rendimiento real NO está determinado por el código de bytes sino por el código de la máquina (que depende de la plataforma). Hagamos un micro benchmark para cada uno de los operadores.
El punto de referencia
Crédito: Según lo aconsejado por @ aleksandr-dubinsky, y gracias a @yura por proporcionar el código base, aquí hay un punto de referencia de JMH (consulte esta guía 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();
}
}
Obtuve los siguientes resultados (el puntaje es una cantidad de operaciones en una unidad de tiempo , por lo tanto, cuanto mayor sea el puntaje, mejor):
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
Advertencia
- El punto de referencia es JVM y depende de la plataforma. Dado que no hay diferencias significativas entre cada operación, es posible obtener un resultado diferente (¡y tal vez un orden diferente!) En una versión y / o plataformas JAVA diferentes como Solaris, Mac o Linux.
- el punto de referencia compara el rendimiento de "es B una instancia de A" cuando "B extiende A" directamente. Si la jerarquía de clases es más profunda y compleja (como B extiende X que extiende Y que extiende Z que extiende A), los resultados pueden ser diferentes.
- Por lo general, se recomienda escribir el código primero seleccionando uno de los operadores (el más conveniente) y luego perfilar su código para verificar si hay un cuello de botella en el rendimiento. Tal vez este operador sea insignificante en el contexto de su código, o tal vez ...
- en relación con el punto anterior,
instanceof
en el contexto de su código podría optimizarse más fácilmente que, isInstance
por ejemplo, ...
Para darle un ejemplo, tome el siguiente 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;
Gracias al JIT, el código se optimiza en algún momento y obtenemos:
- instancia de: 6 ms
- isInstance: 12ms
- isAssignableFrom: 15ms
Nota
Originalmente, esta publicación estaba haciendo su propio punto de referencia utilizando un bucle for en Java sin procesar, que dio resultados poco confiables ya que algunas optimizaciones como Just In Time pueden eliminar el bucle. Por lo tanto, medía principalmente cuánto tiempo tardó el compilador JIT en optimizar el ciclo: consulte Prueba de rendimiento independiente del número de iteraciones para obtener más detalles
Preguntas relacionadas