El impacto en el rendimiento del uso de instanceof en Java

314

Estoy trabajando en una aplicación y un enfoque de diseño implica un uso extremadamente intenso del instanceofoperador. Si bien sé que el diseño OO generalmente trata de evitar el uso instanceof, esa es una historia diferente y esta pregunta está puramente relacionada con el rendimiento. Me preguntaba si hay algún impacto en el rendimiento? Es tan rápido como== ?

Por ejemplo, tengo una clase base con 10 subclases. En una sola función que toma la clase base, verifico si la clase es una instancia de la subclase y realizo alguna rutina.

Una de las otras formas en que pensé en resolverlo fue usar una primitiva entera "type id" en su lugar, y usar una máscara de bits para representar las categorías de las subclases, y luego simplemente hacer una comparación de máscara de bits de las subclases "type id" a un máscara constante que representa la categoría.

¿ instanceofLa JVM está optimizada de alguna manera para que sea más rápida que eso? Quiero seguir Java pero el rendimiento de la aplicación es crítico. Sería genial si alguien que ha estado en este camino antes podría ofrecer algún consejo. ¿Estoy buscando demasiado o me estoy enfocando en lo incorrecto para optimizar?

Josh
fuente
81
Sin embargo, creo que el punto de la pregunta era dejar de lado la cuestión de las mejores prácticas de OO y examinar el rendimiento.
Dave L.
3
@Dave L. Normalmente estaría de acuerdo, pero el OP menciona que está buscando algunas técnicas generales de optimización y no está seguro de si su problema está relacionado con 'instanceof'. Creo que al menos vale la pena mencionar el diseño 'correcto' para que pueda perfilar ambas opciones.
Outlaw Programmer
51
Ugh ... ¿Por qué todas las respuestas pierden el punto de la pregunta y proporcionan la misma retórica de Knuth sobre la optimización? Su pregunta es acerca de si instanceof es significativamente / sorprendentemente más lento que verificar el objeto de clase con ==, y descubrí que no lo es.
gubby
3
El rendimiento de instanceof y casting es bastante bueno. Publiqué algunos tiempos en Java7 alrededor de diferentes enfoques para el problema aquí: stackoverflow.com/questions/16320014/…
Wheezil

Respuestas:

268

Los compiladores JVM / JIC modernos han eliminado el impacto en el rendimiento de la mayoría de las operaciones tradicionalmente "lentas", incluidas instancias, manejo de excepciones, reflexión, etc.

Como escribió Donald Knuth, "Deberíamos olvidarnos de las pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todo mal". El rendimiento de la instancia de probablemente no será un problema, así que no pierdas el tiempo buscando soluciones exóticas hasta que estés seguro de que ese es el problema.

Steve
fuente
13
JVM / JIC moderno ... ¿Podría mencionar de qué versión de Java se han cubierto estas optimizaciones?
Ravisha
138
Siempre hay alguien que cita a Knuth cuando el tema es el rendimiento ... Olvidando que Knuth también declaró (en el mismo artículo) "En las disciplinas de ingeniería establecidas, una mejora del 12%, obtenida fácilmente, nunca se considera marginal y creo que el mismo punto de vista debe prevalecer en la ingeniería de software ", casi todo su trabajo fue sobre la eficiencia de los algoritmos y escribió algoritmos en ensamblaje para (entre otras cosas) lograr un mejor rendimiento. Meh ...
kgadek
44
¿Un lado aquí pero sería try { ObjT o = (ObjT)object } catch (e) { no not one of these }más rápido más lento?
Peter
35
Si "objeto" es una instancia de ObjT, lanzarlo es un poco más rápido que hacer una instancia de, pero la diferencia que encontró mi prueba rápida fue de 10-20 ms en más de 10,000,000 iteraciones. Sin embargo, si "objeto" no es un ObjT, la captura de la excepción fue más de 3000 veces más lenta, más de 31,000 ms frente a ~ 10 ms para la instancia de.
Steve
19
un argumento tan fuerte sin ninguna "referencia" es completamente inútil porque solo es obstinado.
marcorossi
279

Acercarse

Escribí un programa de referencia para evaluar diferentes implementaciones:

  1. instanceof implementación (como referencia)
  2. orientado a objetos a través de una clase abstracta y @Override un método de prueba
  3. utilizando una implementación de tipo propio
  4. getClass() == _.class implementación

Utilicé jmh para ejecutar el punto de referencia con 100 llamadas de calentamiento, 1000 iteraciones bajo medición y con 10 tenedores. Por lo tanto, cada opción se midió con 10 000 veces, lo que lleva 12:18:57 para ejecutar todo el punto de referencia en mi MacBook Pro con macOS 10.12.4 y Java 1.8. El punto de referencia mide el tiempo promedio de cada opción. Para más detalles vea mi implementación en GitHub .

En aras de la exhaustividad: hay una versión anterior de esta respuesta y mi punto de referencia .

Resultados

El | Operación | Tiempo de ejecución en nanosegundos por operación | Relativo a la instancia de |
| ------------ | ------------------------------------ - | ------------------------ |
El | INSTANCIA DE | 39,598 ± 0,022 ns / op | 100,00% |
El | GETCLASS | 39,687 ± 0,021 ns / op | 100,22% |
El | TIPO | 46,295 ± 0,026 ns / op | 116,91% |
El | OO | 48,078 ± 0,026 ns / op | 121,42% |

tl; dr

En Java 1.8 instanceofes el enfoque más rápido, aunque getClass()está muy cerca.

Michael Dorner
fuente
58
+0.(9)¡para la ciencia!
16
+ el otro 0.1 de mi parte: D
Tobias Reich
14
@TobiasReich Entonces tenemos +1.0(9) . :)
Pavel
99
No creo que esto mida nada significativo en absoluto. El código mide el uso System.currentTimeMillis()en una operación que no es mucho más que una sola llamada de método, lo que debería dar mucho a baja precisión. ¡Use un marco de referencia como JMH en su lugar!
Lii
66
O simplemente realice el cronometraje de los mil millones de llamadas en lugar de hacerlo por llamada.
LegendLength
74

Acabo de hacer una prueba simple para ver cómo se compara el rendimiento de la instancia del rendimiento con una simple llamada s.equals () a un objeto de cadena con solo una letra.

en un ciclo de 10.000.000, la instancia Of me dio 63-96 ms, y la cadena igual me dio 106-230 ms

Usé java jvm 6.

Entonces, en mi prueba simple, es más rápido hacer una instancia de comparación de una cadena de caracteres.

El uso de .equals () de Integer en lugar de cadenas me dio el mismo resultado, solo cuando utilicé == i fue más rápido que instanciaO por 20 ms (en un ciclo de 10.000.000)


fuente
44
¿Sería posible que publiques el código aquí? ¡Que sería increíble!
El alquimista
77
¿Cómo se compara instanceOf con el envío de funciones polimórficas?
Chris
21
¿Por qué comparas instanceof con String.equals ()? Si desea verificar el tipo que tiene que object.getClass (). Equals (SomeType.class)
marsbear
44
@marsbear equals()no lo cortará, porque subclasifica; lo que necesita isAssignableFrom().
David Moles
1
@marsbear Correcto, pero esa no es una mejor prueba de lo que estaba preguntando el OP.
David Moles
20

Los elementos que determinarán el impacto en el rendimiento son:

  1. El número de clases posibles para las cuales el operador instanceof podría devolver verdadero
  2. La distribución de sus datos: ¿se resuelven la mayoría de las instancias de operaciones en el primer o segundo intento? Deberá poner primero su probabilidad más alta de devolver operaciones verdaderas.
  3. El entorno de despliegue. La ejecución en una máquina solar Sun Solaris es significativamente diferente de la JVM de Windows de Sun. Solaris se ejecutará en modo 'servidor' de forma predeterminada, mientras que Windows se ejecutará en modo cliente. Las optimizaciones JIT en Solaris harán que todos los métodos de acceso sean iguales.

Creé un microbenchmark para cuatro métodos diferentes de envío . Los resultados de Solaris son los siguientes, siendo el número más pequeño más rápido:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
brianegge
fuente
18

Respondiendo a su última pregunta: a menos que un perfilador le diga que pasa una cantidad ridícula de tiempo en un caso de: Sí, está haciendo trampa.

Antes de preguntarse sobre la optimización de algo que nunca fue necesario optimizar: escriba su algoritmo de la manera más legible y ejecútelo. Ejecútelo, hasta que el compilador jit tenga la oportunidad de optimizarlo por sí mismo. Si luego tiene problemas con este código, use un generador de perfiles para indicarle dónde obtener el mayor beneficio y optimizarlo.

En tiempos de compiladores altamente optimizados, es probable que sus conjeturas sobre los cuellos de botella sean completamente erróneas.

Y en el verdadero espíritu de esta respuesta (que creo sinceramente): no sé cómo se relacionan instanciade y == una vez que el compilador jit tuvo la oportunidad de optimizarlo.

Olvidé: nunca midas la primera carrera.

Olaf Kock
fuente
1
Pero el póster original mencionaba que el rendimiento era crítico para esta aplicación, por lo que no es razonable optimizarlo temprano en esa situación. En otras palabras, no escribirías un juego en 3D en GWBasic y luego, al final, diras, bueno, comencemos a optimizar esto, el primer paso es portarlo a c ++.
LegendLength
GWBasic podría ser un gran comienzo para los juegos en 3D, si hay bibliotecas adecuadas disponibles. Pero aparte de eso (ya que es un argumento artificial): OP no está pidiendo una reescritura completa como optimización. Se trata de una construcción única en la que ni siquiera sabemos si el impacto es significativo (incluso si hay una mejor manera de hacer lo mismo en la versión actual del compilador ). Estoy firmemente de pie detrás de c2.com/cgi/wiki?ProfileBeforeOptimizing y mi respuesta. ¡La optimización preliminar es la raíz de todo mal! Hace que el mantenimiento sea más difícil, y el mantenimiento es el aspecto que vale la pena optimizar
Olaf Kock el
15

Tengo la misma pregunta, pero debido a que no encontré 'métricas de rendimiento' para un caso de uso similar al mío, he hecho más código de muestra. En mi hardware y Java 6 y 7, la diferencia entre instanceof y activar 10mln iteraciones es

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

Entonces, instanceof es realmente más lento, especialmente en un gran número de declaraciones if-else-if, sin embargo, la diferencia será insignificante en la aplicación real.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}
Xtra Coder
fuente
¿Qué resultado fue java 6 y cuál fue java 7? ¿Has vuelto a visitar esto en Java 8? Más significativamente aquí, está comparando una longitud de instancias de lo que es esencial una declaración de caso en ints. Creo que esperaríamos que un interruptor int se alivie rápidamente.
Azeroth2b
1
No puedo recordar exactamente lo que sucedía hace 5 años: creo que tanto Java 6 como Java 7 tuvieron resultados similares, es por eso que solo se proporciona un resultado (siempre que 2 líneas sean para diferentes profundidades de jerarquía de clases) ... y no , no intenté comparar con Java 8. Se proporciona todo el código de prueba: puede copiarlo / pegarlo y verificar en los entornos que necesita (nota: hoy en día usaría la prueba JMH para esto).
Xtra Coder
9

instanceof es realmente rápido, toma solo unas pocas instrucciones de CPU.

Aparentemente, si una clase Xno tiene subclases cargadas (JVM sabe), instanceofse puede optimizar como:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

¡El costo principal es solo una lectura!

Si X tiene subclases cargadas, se necesitan algunas lecturas más; es probable que estén ubicados conjuntamente, por lo que el costo adicional también es muy bajo.

¡Buenas noticias para todos!

irreputable
fuente
2
se puede optimizar o se optimiza? ¿fuente?
@vaxquis puede como su jvm impl específico
RecursiveExceptionException
@itzJanuary suspiro se ha perdido el punto de mi pregunta aquí: todo el mundo sabe que el compilador puede optimizar foo- pero es fooen realidad actualmente optimiza mediante javac de Oracle / VM - o es sólo posible que hará que en el futuro? Además, le pregunté al respondedor si tiene alguna fuente de respaldo (ya sea documentos, código fuente, blog de desarrollo) que documente que realmente puede optimizarse o optimizarse . Sin ella, esta respuesta es solo una reflexión al azar sobre lo que posiblemente puede hacer el compilador .
@vaxquis Nunca mencionó la máquina virtual Hotspot, pero en ese caso no sé si está "optimizada".
RecursiveExceptionException
1
Lea recientemente que JIT (JVM 8) optimizará un sitio de llamadas para 1 o 2 tipos mediante llamadas directas, pero vuelve a la tabla virtual si se encuentran más de dos tipos reales. Por lo tanto, solo tener dos tipos concretos que pasan por un sitio de llamadas en tiempo de ejecución representa una ventaja de rendimiento.
simon.watts
5

Instanceof es muy rápido. Se reduce a un código de bytes que se utiliza para la comparación de referencia de clase. Pruebe algunos millones de instancias en un bucle y compruébelo usted mismo.

Apocalipsis
fuente
5

instancia de probablemente será más costoso que un simple igual en la mayoría de las implementaciones del mundo real (es decir, aquellas en las que realmente se necesita instancia de, y no se puede resolver simplemente anulando un método común, como todos los libros de texto para principiantes, así como Demian sugiere arriba).

¿Porqué es eso? Porque lo que probablemente sucederá es que tiene varias interfaces, que proporcionan alguna funcionalidad (digamos, interfaces x, y y z), y algunos objetos para manipular que pueden (o no) implementar una de esas interfaces ... pero no directamente. Digamos, por ejemplo, que tengo:

w extiende x

A implementa w

B extiende A

C extiende B, implementa y

D extiende C, implementa z

Supongamos que estoy procesando una instancia de D, el objeto d. La computación (d instanceof x) requiere tomar d.getClass (), recorrer las interfaces que implementa para saber si uno es == a x, y si no lo hace nuevamente de forma recursiva para todos sus antepasados ​​... En nuestro caso, Si realiza una primera exploración amplia de ese árbol, produce al menos 8 comparaciones, suponiendo que y y z no extiendan nada ...

Es probable que la complejidad de un árbol de derivación del mundo real sea mayor. En algunos casos, el JIT puede optimizar la mayor parte, si puede resolver de antemano d como, en todos los casos posibles, una instancia de algo que extiende x. Sin embargo, de manera realista, vas a atravesar ese recorrido del árbol la mayor parte del tiempo.

Si eso se convierte en un problema, sugeriría usar un mapa de controlador en su lugar, vinculando la clase concreta del objeto a un cierre que realiza el manejo. Elimina la fase transversal del árbol a favor de un mapeo directo. Sin embargo, tenga en cuenta que si ha configurado un controlador para C.class, mi objeto d anterior no será reconocido.

Aquí están mis 2 centavos, espero que ayuden ...


fuente
5

instanceof es muy eficiente, por lo que es poco probable que su rendimiento se vea afectado. Sin embargo, usar muchas instancias de sugiere un problema de diseño.

Si puede usar xClass == String.class, esto es más rápido. Nota: no necesita instanceof para las clases finales.

Peter Lawrey
fuente
1
Por cierto, ¿qué quieres decir con "no necesito instancia de para las clases finales"?
Pacerier
Una clase final no puede tener subclases. En este caso x.getClass() == Class.classes lo mismo quex instanceof Class
Peter Lawrey
Genial, suponiendo que x no sea nulo, ¿cuál preferirías?
Pacerier
Buen punto. Dependería de si espero xserlo null, supongo. (O lo que sea más claro)
Peter Lawrey
Hmm, me acabo de dar cuenta de que también podríamos usar java.lang.class.isAssignableFrom, ¿sabe si la palabra clave instanceof usa internamente funciones como estas?
Pacerier
4

Generalmente, la razón por la cual el operador "instanceof" está mal visto en un caso como ese (donde la instanciaof está buscando subclases de esta clase base) es porque lo que debería estar haciendo es mover las operaciones a un método y anularlo por el apropiado subclases Por ejemplo, si tienes:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

Puedes reemplazar eso con

o.doEverything();

y luego tener la implementación de "doEverything ()" en la llamada de Clase1 "doThis ()", y en la llamada de Clase2 "doThat ()", y así sucesivamente.

Paul Tomblin
fuente
11
Pero a veces no puedes. Si está implementando una interfaz que lo tiene tomando un Objeto, y necesita saber de qué tipo es, entonces instancia de es realmente la única opción. Podrías intentar lanzar, pero la instancia de es generalmente más limpia.
Herms
4

'instanceof' es en realidad un operador, como + o -, y creo que tiene su propia instrucción de código de bytes JVM. Debería ser bastante rápido.

No debería que si tiene un interruptor donde está probando si un objeto es una instancia de alguna subclase, entonces su diseño podría necesitar ser reelaborado. Considere empujar el comportamiento específico de la subclase hacia las propias subclases.

Programador ilegal
fuente
4

Demian y Paul mencionan un buen punto; sin embargo , la ubicación del código para ejecutar realmente depende de cómo desea utilizar los datos ...

Soy un gran admirador de los objetos de datos pequeños que se pueden usar de muchas maneras. Si sigue el enfoque de anulación (polimórfico), sus objetos solo se pueden usar "de una manera".

Aquí es donde entran los patrones ...

Puede usar el despacho doble (como en el patrón de visitante) para pedirle a cada objeto que "lo llame" que se pasa a sí mismo; esto resolverá el tipo de objeto. sin embargo (nuevamente) necesitará una clase que pueda "hacer cosas" con todos los subtipos posibles.

Prefiero usar un patrón de estrategia, donde puedes registrar estrategias para cada subtipo que quieras manejar. Algo como lo siguiente. Tenga en cuenta que esto solo ayuda para las coincidencias de tipo exactas, pero tiene la ventaja de que es extensible: los contribuyentes externos pueden agregar sus propios tipos y controladores. (Esto es bueno para marcos dinámicos como OSGi, donde se pueden agregar nuevos paquetes)

Esperemos que esto inspire algunas otras ideas ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}
Scott Stanchfield
fuente
4

Escribo una prueba de rendimiento basada en jmh-java-benchmark-archetype: 2.21. JDK es openjdk y la versión es 1.8.0_212. La máquina de prueba es mac pro. El resultado de la prueba es:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

El resultado muestra que: getClass es mejor que instanceOf, lo cual es contrario a otras pruebas. Sin embargo, no sé por qué.

El código de prueba está abajo:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}
salexinx
fuente
Si tuviera que especular, lo que hace es posiblemente más complejo. Una comprobación getClass () == realizará una comprobación precisa 1: 1, donde instancia de comprueba una jerarquía, es decir, myHashSet instanceof Collection pasaría, pero myHashSet.getClass () == Collection.class no lo haría. Esencialmente, no son operaciones equivalentes, por lo que no estoy demasiado sorprendido de que el rendimiento también sea diferente.
AMTerp
3

Es difícil decir cómo implementa una determinada instancia de JVM, pero en la mayoría de los casos, los objetos son comparables a las estructuras y las clases también, y cada estructura de objeto tiene un puntero a la estructura de clase de la que es una instancia. Así que en realidad instancia de para

if (o instanceof java.lang.String)

podría ser tan rápido como el siguiente código C

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

suponiendo que un compilador JIT esté en su lugar y haga un trabajo decente.

Teniendo en cuenta que esto solo es acceder a un puntero, obtener un puntero en un cierto desplazamiento al que apunta el puntero y compararlo con otro puntero (que es básicamente lo mismo que probar que los números de 32 bits sean iguales), diría que la operación puede realmente Se muy rápido.

Sin embargo, no tiene que depender mucho de la JVM. Sin embargo, si esto fuera la operación de cuello de botella en su código, consideraría que la implementación de JVM es bastante pobre. Incluso uno que no tenga un compilador JIT y solo interprete el código debería ser capaz de hacer una instancia de prueba prácticamente en ningún momento.

Mecki
fuente
1
¿No tiene que averiguar si o hereda de java.lang.String?
WW.
1
Es por eso que dije que "podría" ser tan rápido. En realidad, realiza un ciclo, primero verificando iAmInstanceOf contra la clase en cuestión, luego sube hacia arriba el árbol de herencia de o y repite esta verificación para cada superclase de o (por lo que podría tener que ejecutar este ciclo un par de veces para un partido)
Mecki
3

Me pondré en contacto con usted en caso de rendimiento. Pero una forma de evitar el problema (o la falta del mismo) sería crear una interfaz principal para todas las subclases en las que necesita hacer instancia. La interfaz será un superconjunto de todos los métodos en las subclases para los que necesita hacer una instancia de verificación. Cuando un método no se aplica a una subclase específica, simplemente proporcione una implementación ficticia de este método. Si no entendí mal el problema, así es como lo he solucionado en el pasado.

Jose quijada
fuente
2

InstanceOf es una advertencia de un mal diseño orientado a objetos.

Las JVM actuales significan que la instancia de no es una gran preocupación de rendimiento en sí misma. Si te encuentras usándolo mucho, especialmente para la funcionalidad principal, probablemente sea hora de mirar el diseño. Las ganancias de rendimiento (y simplicidad / mantenibilidad) de la refactorización para un mejor diseño superarán en gran medida los ciclos de procesador reales gastados en la instancia real llamada.

Para dar un ejemplo muy pequeño de programación simplista.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

Es una arquitectura pobre, una mejor opción hubiera sido tener SomeObject como la clase principal de dos clases secundarias donde cada clase secundaria anula un método (doSomething) para que el código se vea así:

Someobject.doSomething();
Demian Krige
fuente
61
Estoy al tanto. Eso no fue lo que pregunté.
Josh
No está seguro de si votar o no con esto, ya que es un punto bueno, pero no responde a la pregunta hecha ...
jklp
77
Creo que el ejemplo de código es realmente muy malo: no se puede extender la clase Double, y tampoco se puede derivar Double de otra clase. Si hubiera usado otras clases para el ejemplo, hubiera estado bien.
Lena Schimmel el
66
Además, si las clases secundarias de SomeObject son objetos de valor, entonces no desea poner la lógica en ellos. Por ejemplo, Pie and Roast podría no ser el lugar correcto para la lógica putInOven () y putInMouth ().
sk.
Sin embargo
binboavetonik
2

En la versión moderna de Java, el operador instanceof es más rápido como una simple llamada de método. Esto significa:

if(a instanceof AnyObject){
}

es más rápido como:

if(a.getType() == XYZ){
}

Otra cosa es si necesita en cascada muchas instancias de. Entonces, un interruptor que solo llama una vez getType () es más rápido.

Horrocrux7
fuente
1

Si la velocidad es su único objetivo, entonces el uso de constantes int para identificar subclases parece reducir milisegundos de tiempo

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

terrible diseño de OO, pero si su análisis de rendimiento indica que aquí es donde está el cuello de botella, entonces tal vez. En mi código, el código de envío toma el 10% del tiempo total de ejecución y esto puede contribuir a una mejora de la velocidad total del 1%.

Salix alba
fuente
0

Debe medir / perfilar si realmente es un problema de rendimiento en su proyecto. Si es así, recomendaría un rediseño, si es posible. Estoy bastante seguro de que no puedes superar la implementación nativa de la plataforma (escrita en C). También debe considerar la herencia múltiple en este caso.

Debería contar más sobre el problema, tal vez podría usar una tienda asociativa, por ejemplo, un Mapa <Clase, Objeto> si solo está interesado en los tipos concretos.

Karl
fuente
0

Con respecto a la nota de Peter Lawrey de que no necesita instancia para las clases finales y solo puede usar una igualdad de referencia, ¡tenga cuidado! Aunque las clases finales no se pueden extender, no se garantiza que sean cargadas por el mismo cargador de clases. Solo use x.getClass () == SomeFinal.class o similar si está absolutamente seguro de que solo hay un cargador de clases en juego para esa sección de código.


fuente
44
Si una clase es cargada por un cargador de clases diferente, no creo que instancia de coincida tampoco.
Peter Lawrey
0

También prefiero un enfoque enum, pero usaría una clase base abstracta para forzar a las subclases a implementar el getType()método.

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}
Miguel
fuente
0

Pensé que podría valer la pena presentar un contraejemplo al consenso general en esta página de que "instanceof" no es lo suficientemente caro como para preocuparse. Descubrí que tenía un código en un bucle interno que (en algún intento histórico de optimización)

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

donde llamar a head () en un SingleItem devuelve el valor sin cambios. Reemplazando el código por

seq = seq.head();

me da una aceleración de 269 ms a 169 ms, a pesar del hecho de que suceden algunas cosas bastante pesadas en el bucle, como la conversión de cadena a doble. Por supuesto, es posible que la aceleración se deba más a eliminar la rama condicional que a eliminar la instancia del operador en sí; pero pensé que valía la pena mencionarlo.

Michael Kay
fuente
Esto podría ser por ifsí mismo. Si la distribución de truesys falsees casi par, la ejecución especulativa se vuelve inútil, lo que conduce a retrasos significativos.
Dmytro
-4

Te estás centrando en lo incorrecto. La diferencia entre instanceof y cualquier otro método para verificar lo mismo probablemente ni siquiera sería medible. Si el rendimiento es crítico, entonces Java probablemente sea el lenguaje incorrecto. La razón principal es que no puede controlar cuándo la VM decide que quiere ir a recoger basura, lo que puede llevar la CPU al 100% durante varios segundos en un programa grande (MagicDraw 10 fue genial para eso). A menos que tenga el control de todas las computadoras en las que se ejecutará este programa, no puede garantizar en qué versión de JVM estará, y muchas de las más antiguas tenían problemas importantes de velocidad. Si es una aplicación pequeña, puede estar bien con Java, pero si está constantemente leyendo y descartando datos , notará cuando se active el GC.

tloach
fuente
77
Eso es mucho menos cierto de los algoritmos de recolección de basura java más modernos de lo que solía ser. Incluso a los algoritmos más simples ya no les importa la cantidad de memoria que desecha justo después de usarla; solo les importa la cantidad retenida en las colecciones de generaciones jóvenes.
Bill Michell
3
Genial, excepto que estoy en la JVM más reciente y mi computadora aún se arrastra cuando se ejecuta el GC. En un servidor RAM de doble núcleo y 3 GB. Java no es un lenguaje para usar si el rendimiento realmente importa.
tloach
@David: no es necesario que requiera problemas en tiempo real cuando su aplicación desaparece por un período de tiempo. Una divertida que he encontrado es una aplicación Java que se conectó a un flujo TCP que murió cuando se ejecutó el GC porque no cerró el flujo primero y no pudo manejar la sobrecarga del tráfico de la red cuando regresó, lo haría inmediatamente entrar en un bucle donde se ejecuta el GC, cuando la aplicación se reanuda, trata de generar un montón de datos, lo que hace que se quede sin memoria, lo que desencadenó el GC, etc. Java es ideal para muchas tareas, pero no para tareas donde Se requiere un buen rendimiento.
tloach
66
@tloach me parece un mal diseño de la aplicación. Hablas de "rendimiento" como si fuera unidimensional. He trabajado con (y en) un montón de aplicaciones Java que, por ejemplo, fueron eficaces para proporcionar análisis estadísticos interactivos ágiles y visualización de conjuntos de datos muy grandes, o bien para procesar volúmenes de transacciones muy grandes muy rápidamente. "rendimiento" no es solo una cosa, y el hecho de que alguien pueda escribir una aplicación que maneje mal la memoria y permita que GC se interponga a su manera no significa que algo que requiera "rendimiento" deba escribirse en otra cosa.
David Moles