En Java, usar tirar / atrapar como parte de la lógica cuando en realidad no hay un error es generalmente una mala idea (en parte) porque lanzar y atrapar una excepción es costoso, y hacerlo muchas veces en un ciclo suele ser mucho más lento que otro estructuras de control que no implican lanzar excepciones.
Mi pregunta es, ¿es el costo incurrido en el lanzamiento / captura en sí, o al crear el objeto Exception (ya que obtiene mucha información de tiempo de ejecución, incluida la pila de ejecución)?
En otras palabras, si lo hago
Exception e = new Exception();
pero no lo lance, ¿es eso la mayor parte del costo del lanzamiento, o el manejo de lanzar + atrapar es lo que es costoso?
No estoy preguntando si poner código en un bloque try / catch aumenta el costo de ejecutar ese código, estoy preguntando si atrapar la Excepción es la parte costosa o crear (llamar al constructor) la Excepción es la parte costosa .
Otra forma de preguntar esto es, si hice una instancia de Exception y la lancé y la atrapé una y otra vez, ¿sería significativamente más rápido que crear una nueva Excepción cada vez que lanzo?
fuente
Respuestas:
Crear un objeto de excepción no es más costoso que crear otros objetos regulares. El costo principal está oculto en el
fillInStackTrace
método nativo que recorre la pila de llamadas y recopila toda la información requerida para construir un seguimiento de la pila: clases, nombres de métodos, números de línea, etc.El mito sobre los altos costos de excepción proviene del hecho de que la mayoría de los
Throwable
constructores lo llaman implícitamentefillInStackTrace
. Sin embargo, hay un constructor para crear unThrowable
sin un seguimiento de pila. Le permite crear objetos arrojables que son muy rápidos para crear instancias. Otra forma de crear excepciones ligeras es anularfillInStackTrace
.¿Y qué hay de lanzar una excepción?
De hecho, depende de donde está una excepción lanzada atrapado .
Si se detecta en el mismo método (o, más precisamente, en el mismo contexto, ya que el contexto puede incluir varios métodos debido a la alineación), entonces
throw
es tan rápido y simple comogoto
(por supuesto, después de la compilación JIT).Sin embargo, si un
catch
bloque está en algún lugar más profundo de la pila, JVM necesita desenrollar los marcos de la pila, y esto puede llevar mucho más tiempo. Se tarda aún más, si haysynchronized
bloques o métodos involucrados, porque desenrollar implica la liberación de monitores propiedad de los marcos de pila eliminados.Podría confirmar las declaraciones anteriores con los puntos de referencia adecuados, pero afortunadamente no necesito hacer esto, ya que todos los aspectos ya están perfectamente cubiertos en la publicación del ingeniero de rendimiento de HotSpot, Alexey Shipilev: El rendimiento excepcional de Lil 'Exception .
fuente
La primera operación en la mayoría de los
Throwable
constructores es completar el seguimiento de la pila, que es donde está la mayor parte del gasto.Sin embargo, hay un constructor protegido con una bandera para deshabilitar el seguimiento de la pila. Este constructor también es accesible cuando se extiende
Exception
. Si crea un tipo de excepción personalizado, puede evitar la creación del seguimiento de la pila y obtener un mejor rendimiento a expensas de menos información.Si crea una única excepción de cualquier tipo por medios normales, puede volver a lanzarla muchas veces sin la sobrecarga de completar el seguimiento de la pila. Sin embargo, su seguimiento de pila reflejará dónde fue construido, no dónde fue arrojado en una instancia particular.
Las versiones actuales de Java hacen algunos intentos para optimizar la creación de seguimiento de pila. El código nativo se invoca para completar el seguimiento de la pila, que registra el seguimiento en una estructura nativa más ligera. Java correspondientes
StackTraceElement
objetos se crean a partir de perezosamente este registro sólo cuando losgetStackTrace()
,printStackTrace()
son llamados, u otros métodos que requieren la traza.Si elimina la generación de seguimiento de la pila, el otro costo principal es desenrollar la pila entre el lanzamiento y la captura. Cuantas menos tramas intermedias se encuentren antes de que se detecte la excepción, más rápido será.
Diseñe su programa para que las excepciones solo se produzcan en casos verdaderamente excepcionales, y las optimizaciones como estas son difíciles de justificar.
fuente
Hay un buen artículo sobre Excepciones aquí.
http://shipilev.net/blog/2014/exceptional-performance/
La conclusión es que la construcción de trazas de pila y el desbobinado de la pila son las partes caras. El siguiente código aprovecha una característica en la
1.7
que podemos activar y desactivar los seguimientos de pila. Luego podemos usar esto para ver qué tipo de costos tienen diferentes escenariosLos siguientes son tiempos para la creación de objetos solo. He agregado
String
aquí para que pueda ver que sin la pila escrita, casi no hay diferencia en la creación de unJavaException
Objeto y unString
. Con la escritura en pila activada, la diferencia es dramática, es decir, al menos un orden de magnitud más lento.A continuación se muestra el tiempo que se tardó en regresar de un lanzamiento a una profundidad particular un millón de veces.
Lo siguiente es casi seguro una gran simplificación ...
Si tomamos una profundidad de 16 con la escritura de la pila activada, la creación de objetos lleva aproximadamente ~ 40% del tiempo, el seguimiento real de la pila representa la gran mayoría de esto. ~ El 93% de la creación de instancias del objeto JavaException se debe al seguimiento de la pila que se está tomando. Esto significa que desenrollar la pila en este caso lleva el otro 50% del tiempo.
Cuando desactivamos la creación de objetos de rastreo de pila, representa una fracción mucho menor, es decir, el 20%, y el desenrollado de pila ahora representa el 80% del tiempo.
En ambos casos, el desbobinado de la pila ocupa una gran parte del tiempo total.
Los marcos de pila en este ejemplo son pequeños en comparación con lo que normalmente encontraría.
Puedes echar un vistazo al bytecode usando javap
es decir, esto es para el método 4 ...
fuente
La creación de
Exception
con unnull
seguimiento de pila lleva casi tanto tiempo como el bloquethrow
ytry-catch
juntos. Sin embargo, completar el seguimiento de la pila tarda en promedio 5 veces más .Creé el siguiente punto de referencia para demostrar el impacto en el rendimiento. Agregué
-Djava.compiler=NONE
a la Configuración de ejecución para deshabilitar la optimización del compilador. Para medir el impacto de construir el seguimiento de la pila, amplié laException
clase para aprovechar el constructor sin pila:El código de referencia es el siguiente:
Salida:
Esto implica que crear un
NoStackException
es aproximadamente tan costoso como tirar repetidamente lo mismoException
. También muestra que crearException
y completar su seguimiento de pila tarda aproximadamente 4 veces más.fuente
Esta parte de la pregunta ...
Parece preguntarse si crear una excepción y almacenarla en caché en algún lugar mejora el rendimiento. Si lo hace Es lo mismo que apagar la pila que se escribe en la creación de objetos porque ya se ha hecho.
Estos son los tiempos que tengo, por favor lea la advertencia después de esto ...
Por supuesto, el problema con esto es que su rastro de pila ahora apunta a dónde instanciaron el objeto, no desde dónde fue arrojado.
fuente
Usando la respuesta de @ AustinD como punto de partida, realicé algunos ajustes. Código en la parte inferior.
Además de agregar el caso donde se lanza una instancia de Excepción repetidamente, también desactivé la optimización del compilador para que podamos obtener resultados de rendimiento precisos. Agregué
-Djava.compiler=NONE
a los argumentos de VM, según esta respuesta . (En eclipse, edite Ejecutar configuración → Argumentos para establecer este argumento de VM)Los resultados:
Por lo tanto, crear la excepción cuesta aproximadamente 5 veces más que lanzarlo y atraparlo. Suponiendo que el compilador no optimiza gran parte del costo.
A modo de comparación, aquí está la misma ejecución de prueba sin deshabilitar la optimización:
Código:
fuente