Estoy trabajando en una aplicación y un enfoque de diseño implica un uso extremadamente intenso del instanceof
operador. 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.
¿ instanceof
La 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?
fuente
Respuestas:
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.
fuente
try { ObjT o = (ObjT)object } catch (e) { no not one of these }
más rápido más lento?Acercarse
Escribí un programa de referencia para evaluar diferentes implementaciones:
instanceof
implementación (como referencia)@Override
un método de pruebagetClass() == _.class
implementaciónUtilicé 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
tl; dr
En Java 1.8
instanceof
es el enfoque más rápido, aunquegetClass()
está muy cerca.fuente
+0.(9)
¡para la ciencia!+1.0(9)
. :)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!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
equals()
no lo cortará, porque subclasifica; lo que necesitaisAssignableFrom()
.Los elementos que determinarán el impacto en el rendimiento son:
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:
fuente
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.
fuente
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
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.
fuente
instanceof
es realmente rápido, toma solo unas pocas instrucciones de CPU.Aparentemente, si una clase
X
no tiene subclases cargadas (JVM sabe),instanceof
se puede optimizar como:¡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!
fuente
foo
- pero esfoo
en 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 .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.
fuente
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
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.
fuente
x.getClass() == Class.class
es lo mismo quex instanceof Class
x
serlonull
, supongo. (O lo que sea más claro)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:
Puedes reemplazar eso con
y luego tener la implementación de "doEverything ()" en la llamada de Clase1 "doThis ()", y en la llamada de Clase2 "doThat ()", y así sucesivamente.
fuente
'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.
fuente
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 ...
fuente
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:
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:
fuente
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
podría ser tan rápido como el siguiente código C
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.
fuente
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.
fuente
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.
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í:
fuente
En la versión moderna de Java, el operador instanceof es más rápido como una simple llamada de método. Esto significa:
es más rápido como:
Otra cosa es si necesita en cascada muchas instancias de. Entonces, un interruptor que solo llama una vez getType () es más rápido.
fuente
Si la velocidad es su único objetivo, entonces el uso de constantes int para identificar subclases parece reducir milisegundos de tiempo
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%.
fuente
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.
fuente
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
También prefiero un enfoque enum, pero usaría una clase base abstracta para forzar a las subclases a implementar el
getType()
método.fuente
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)
donde llamar a head () en un SingleItem devuelve el valor sin cambios. Reemplazando el código por
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.
fuente
if
sí mismo. Si la distribución detrue
sysfalse
es casi par, la ejecución especulativa se vuelve inútil, lo que conduce a retrasos significativos.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.
fuente