todas tipos salida propagacion lista las jerarquia excepciones errores entrada ejemplos java performance exception-handling

salida - tipos de excepciones en java netbeans



¿Qué tan lentas son las excepciones de Java? (17)

Pregunta: ¿El manejo de excepciones en Java es realmente lento?

La sabiduría convencional, así como una gran cantidad de resultados de Google, dice que la lógica excepcional no debe usarse para el flujo normal de programas en Java. Normalmente se dan dos razones,

  1. es realmente lento, incluso un orden de magnitud más lento que el código regular (las razones dadas varían)

y

  1. es complicado porque la gente espera que solo los errores se manejen en un código excepcional.

Esta pregunta es sobre el # 1.

Como ejemplo, esta página describe el manejo de excepciones de Java como "muy lento" y relaciona la lentitud con la creación de la cadena del mensaje de excepción: "esta cadena se usa para crear el objeto de excepción que se lanza. Esto no es rápido". El artículo El manejo efectivo de excepciones en Java dice que "la razón de esto se debe al aspecto de creación de objetos del manejo de excepciones, lo que hace que las excepciones de lanzamiento sean intrínsecamente lentas". Otra razón es que la generación de traza de pila es lo que la ralentiza.

Mis pruebas (utilizando Java 1.6.0_07, Java HotSpot 10.0, en Linux de 32 bits) indican que el manejo de excepciones no es más lento que el código normal. Intenté ejecutar un método en un bucle que ejecuta algún código. Al final del método, uso un booleano para indicar si debo devolver o lanzar . De esta manera el procesamiento real es el mismo. Intenté ejecutar los métodos en diferentes órdenes y promediando mis tiempos de prueba, pensando que podría haber sido el calentamiento de la JVM. En todas mis pruebas, el lanzamiento fue al menos tan rápido como el retorno, si no más rápido (hasta un 3,1% más rápido). Estoy completamente abierto a la posibilidad de que mis pruebas estuvieran equivocadas, pero no he visto nada como el ejemplo de código, las comparaciones de pruebas o los resultados en el último año o dos que muestren el manejo de excepciones en Java para ser realmente lento.

Lo que me lleva por este camino fue una API que necesitaba para usar que arrojó excepciones como parte de la lógica de control normal. Quería corregirlos en su uso, pero ahora tal vez no pueda. ¿Tendré que felicitarlos por su visión de futuro?

En el artículo sobre el manejo eficiente de la excepción de Java eficiente en la compilación justo a tiempo , los autores sugieren que la presencia de controladores de excepción solo, incluso si no se producen excepciones, es suficiente para evitar que el compilador JIT optimice el código correctamente, por lo que disminuye . Todavía no he probado esta teoría.


Aleksey Shipilëv realizó un análisis muy completo en el que evalúa las excepciones de Java en diversas combinaciones de condiciones:

  • Excepciones creadas recientemente vs excepciones creadas previamente
  • Seguimiento de pila habilitado vs deshabilitado
  • Rastreo de pila solicitado vs nunca solicitado
  • Atrapado en el nivel superior vs retornar en cada nivel vs encadenado / envuelto en cada nivel
  • Varios niveles de profundidad de pila de llamadas Java
  • Sin optimizaciones de alineación vs configuraciones extremas de alineación vs predeterminadas
  • Campos definidos por el usuario leer vs no leer

También los compara con el rendimiento de verificar un código de error en varios niveles de frecuencia de error.

Las conclusiones (citadas textualmente de su publicación) fueron:

  1. Excepciones verdaderamente excepcionales son bellamente bellas. Si los usa como están diseñados y solo comunica los casos verdaderamente excepcionales entre la gran cantidad de casos no excepcionales que maneja el código regular, entonces el uso de excepciones es la ganancia de rendimiento.

  2. Los costos de rendimiento de las excepciones tienen dos componentes principales: la construcción de seguimiento de pila cuando se crea una instancia de Exception y el desenrollado de la pila durante el lanzamiento de Exception.

  3. Los costos de construcción del rastreo de la pila son proporcionales a la profundidad de la pila en el momento de la creación de instancias de excepción. Eso ya es malo porque ¿quién en la Tierra conoce la profundidad de la pila a la que se llamaría este método de lanzamiento? Incluso si desactiva la generación de seguimiento de pila y / o guarda en caché las excepciones, solo puede deshacerse de esta parte del costo de rendimiento.

  4. Los costos de recuperación de la pila dependen de la suerte que tengamos al acercar el controlador de excepciones al código compilado. La estructura cuidadosa del código para evitar una búsqueda profunda de controladores de excepciones probablemente nos ayude a tener más suerte.

  5. Si eliminamos ambos efectos, el costo de rendimiento de las excepciones es el de la sucursal local. No importa lo hermoso que parezca, eso no significa que debas usar Excepciones como el flujo de control habitual, porque en ese caso estás a la merced de optimizar el compilador. Solo debe usarlos en casos verdaderamente excepcionales, donde la frecuencia de excepción amortiza el posible costo desafortunado de elevar la excepción real.

  6. La regla de oro optimista parece ser una frecuencia de 10 ^ -4 , por lo que las excepciones son lo suficientemente excepcionales. Eso, por supuesto, depende del peso de las excepciones en sí mismas, de las acciones exactas tomadas en los controladores de excepciones, etc.

El resultado es que cuando no se lanza una excepción, no se paga un costo, por lo tanto, cuando la condición excepcional es lo suficientemente raro, el manejo de excepciones es más rápido que usar un if cada vez. El post completo vale mucho la pena leerlo.


Creo que el primer artículo se refiere al acto de atravesar la pila de llamadas y crear una traza de pila como parte costosa, y aunque el segundo artículo no lo dice, creo que es la parte más costosa de la creación de objetos. John Rose tiene un artículo donde describe diferentes técnicas para acelerar las excepciones . (Preasignación y reutilización de una excepción, excepciones sin seguimientos de pila, etc.)

Pero aún así, creo que esto debería considerarse solo como un mal necesario, como último recurso. La razón de John para hacer esto es emular características en otros idiomas que todavía no están disponibles en la JVM. NO debe adquirir el hábito de usar excepciones para el flujo de control. Especialmente no por razones de rendimiento! Como usted mismo menciona en el # 2, corre el riesgo de ocultar errores serios en su código de esta manera, y será más difícil de mantener para los nuevos programadores.

Las micro-marcas en Java son sorprendentemente difíciles de acertar (me han dicho), especialmente cuando entras en el territorio JIT, así que realmente dudo que usar excepciones sea más rápido que "regresar" en la vida real. Por ejemplo, ¿sospecho que tienes entre 2 y 5 cuadros en tu prueba? Ahora imagine que su código será invocado por un componente JSF implementado por JBoss. Ahora es posible que tenga una traza de pila de varias páginas.

Tal vez usted podría publicar su código de prueba?


Depende de cómo se implementen las excepciones. La forma más sencilla es usar setjmp y longjmp. Eso significa que todos los registros de la CPU se escriben en la pila (lo que ya lleva un tiempo) y posiblemente se deban crear otros datos ... todo esto ya ocurre en la declaración de prueba. La instrucción throw debe desenrollar la pila y restaurar los valores de todos los registros (y otros posibles valores en la VM). Así que intentar y lanzar son igualmente lentos, y eso es bastante lento, sin embargo, si no se lanza una excepción, salir del bloque de prueba no toma tiempo en absoluto en la mayoría de los casos (ya que todo se pone en la pila que se limpia automáticamente si el método existe).

Sun y otros reconocieron que esto es posiblemente subóptimo y, por supuesto, las máquinas virtuales se vuelven más y más rápidas con el tiempo. Hay otra forma de implementar excepciones, lo que hace que el intento en sí sea muy rápido (en realidad, no pasa nada para intentarlo en general; todo lo que debe suceder ya está hecho cuando la VM carga la clase) y hace que el lanzamiento no sea tan lento. . No sé qué JVM usa esta nueva y mejor técnica ...

... ¿pero está escribiendo en Java para que su código más adelante solo se ejecute en una JVM en un sistema específico? Dado que si alguna vez se ejecuta en cualquier otra plataforma o cualquier otra versión de JVM (posiblemente de cualquier otro proveedor), ¿quién dice que también usan la implementación rápida? El rápido es más complicado que el lento y no es fácil en todos los sistemas. ¿Quieres mantenerte portátil? Entonces no confíes en que las excepciones sean rápidas.

También hace una gran diferencia lo que haces dentro de un bloque try. Si abre un bloque de prueba y nunca llama a ningún método desde este bloque de prueba, el bloque de prueba será ultra rápido, ya que el JIT puede tratar un tiro como un simple goto. No necesita guardar el estado de la pila ni tiene que desenrollar la pila si se lanza una excepción (solo necesita saltar a los controladores de captura). Sin embargo, esto no es lo que normalmente haces. Por lo general, abre un bloque de prueba y luego llama a un método que podría generar una excepción, ¿verdad? E incluso si solo usas el bloque try dentro de tu método, ¿qué tipo de método será este, que no llame a ningún otro método? ¿Se acaba de calcular un número? Entonces, ¿para qué necesitas excepciones? Hay formas mucho más elegantes de regular el flujo del programa. Para casi cualquier otra cosa que no sea matemática simple, tendrá que llamar a un método externo y esto ya destruye la ventaja de un bloque try local.

Vea el siguiente código de prueba:

public class Test { int value; public int getValue() { return value; } public void reset() { value = 0; } // Calculates without exception public void method1(int i) { value = ((value + i) / i) << 1; // Will never be true if ((i & 0xFFFFFFF) == 1000000000) { System.out.println("You''ll never see this!"); } } // Could in theory throw one, but never will public void method2(int i) throws Exception { value = ((value + i) / i) << 1; // Will never be true if ((i & 0xFFFFFFF) == 1000000000) { throw new Exception(); } } // This one will regularly throw one public void method3(int i) throws Exception { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new Exception(); } } public static void main(String[] args) { int i; long l; Test t = new Test(); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { t.method1(i); } l = System.currentTimeMillis() - l; System.out.println( "method1 took " + l + " ms, result was " + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method2(i); } catch (Exception e) { System.out.println("You''ll never see this!"); } } l = System.currentTimeMillis() - l; System.out.println( "method2 took " + l + " ms, result was " + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method3(i); } catch (Exception e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println( "method3 took " + l + " ms, result was " + t.getValue() ); } }

Resultado:

method1 took 972 ms, result was 2 method2 took 1003 ms, result was 2 method3 took 66716 ms, result was 2

La desaceleración del bloque try es demasiado pequeña para descartar factores de confusión como los procesos en segundo plano. ¡Pero el bloque de captura lo mató todo y lo hizo 66 veces más lento!

Como dije, el resultado no será tan malo si pones try / catch y lanzas todo dentro del mismo método (method3), pero esta es una optimización de JIT especial en la que no confiaría. E incluso cuando se utiliza esta optimización, el lanzamiento sigue siendo bastante lento. Así que no sé qué intentas hacer aquí, pero definitivamente hay una mejor manera de hacerlo que usar try / catch / throw.


El rendimiento excepcional en Java y C # deja mucho que desear.

Como programadores, esto nos obliga a cumplir con la regla "las excepciones deben ser causadas con poca frecuencia", simplemente por razones prácticas de rendimiento.

Sin embargo, como científicos informáticos, deberíamos rebelarnos contra este estado problemático. La persona que crea una función a menudo no tiene idea de la frecuencia con la que se llamará, o si es más probable el éxito o el fracaso. Sólo la persona que llama tiene esta información. Tratar de evitar excepciones lleva a idoms de API poco claros donde en algunos casos solo tenemos versiones de excepciones limpias pero lentas, y en otros tenemos errores de valor de retorno rápidos pero voluminosos, y en otros casos terminamos con ambos . El implementador de la biblioteca puede tener que escribir y mantener dos versiones de las API, y la persona que llama debe decidir cuál de las dos versiones usar en cada situación.

Esto es una especie de desastre. Si las excepciones tuvieran un mejor rendimiento, podríamos evitar estos modismos idiomáticos y usar excepciones, ya que estaban destinadas a ser utilizadas ... como un servicio de devolución de errores estructurado.

Realmente me gustaría ver mecanismos de excepción implementados usando técnicas más cercanas a los valores de retorno, por lo que podríamos tener un rendimiento más cercano a los valores de retorno ... ya que esto es a lo que recurrimos en código sensible al rendimiento.

Aquí hay un ejemplo de código que compara el rendimiento de excepción con el rendimiento de error-valor-valor.

clase pública TestIt {

int value; public int getValue() { return value; } public void reset() { value = 0; } public boolean baseline_null(boolean shouldfail, int recurse_depth) { if (recurse_depth <= 0) { return shouldfail; } else { return baseline_null(shouldfail,recurse_depth-1); } } public boolean retval_error(boolean shouldfail, int recurse_depth) { if (recurse_depth <= 0) { if (shouldfail) { return false; } else { return true; } } else { boolean nested_error = retval_error(shouldfail,recurse_depth-1); if (nested_error) { return true; } else { return false; } } } public void exception_error(boolean shouldfail, int recurse_depth) throws Exception { if (recurse_depth <= 0) { if (shouldfail) { throw new Exception(); } } else { exception_error(shouldfail,recurse_depth-1); } } public static void main(String[] args) { int i; long l; TestIt t = new TestIt(); int failures; int ITERATION_COUNT = 100000000; // (0) baseline null workload for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; t.baseline_null(shoulderror,recurse_depth); } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms/n", recurse_depth, exception_freq, failures,elapsed_time); } } // (1) retval_error for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; if (!t.retval_error(shoulderror,recurse_depth)) { failures++; } } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms/n", recurse_depth, exception_freq, failures,elapsed_time); } } // (2) exception_error for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; try { t.exception_error(shoulderror,recurse_depth); } catch (Exception e) { failures++; } } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms/n", recurse_depth, exception_freq, failures,elapsed_time); } } }

}

Y aquí están los resultados:

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121 ms retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141 ms retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334 ms retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487 ms exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763 ms exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367 ms exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775 ms exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116 ms exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

La comprobación y la propagación de los valores de retorno agregan algún costo frente a la llamada de línea de base nula, y ese costo es proporcional a la profundidad de la llamada. A una profundidad de la cadena de llamadas de 8, la versión de comprobación de error-valor-retorno fue aproximadamente un 27% más lenta que la versión de línea base que no verificó los valores de retorno.

El rendimiento de la excepción, en comparación, no es una función de profundidad de llamada, sino de frecuencia de excepción. Sin embargo, la degradación a medida que aumenta la frecuencia de excepciones es mucho más dramática. Con solo un 25% de frecuencia de error, el código se ejecutó 24-VECES más lento. Con una frecuencia de error del 100%, la versión de excepción es casi 100 veces más lenta.

Esto me sugiere que quizás estamos haciendo las compensaciones incorrectas en nuestras implementaciones de excepción. Las excepciones podrían ser más rápidas, ya sea evitando los costosos paseos o acosándolos en una verificación de valor de retorno compatible con el compilador. Hasta que lo hagan, estamos atascados evitándolos cuando queremos que nuestro código se ejecute rápidamente.


Hace un tiempo escribí una clase para probar el rendimiento relativo de convertir cadenas a ints usando dos enfoques: (1) llamar a Integer.parseInt () y capturar la excepción, o (2) hacer coincidir la cadena con una expresión regular y llamar a parseInt () Sólo si el partido tiene éxito. Utilicé la expresión regular de la manera más eficiente que pude (es decir, creando los objetos Pattern y Matcher antes de interrumpir el ciclo), y no imprimí ni guardé los stacktraces de las excepciones.

Para una lista de diez mil cadenas, si todos fueran números válidos, el enfoque parseInt () fue cuatro veces más rápido que el enfoque de expresiones regulares. Pero si solo el 80% de las cadenas eran válidas, la expresión regular era dos veces más rápida que parseInt (). Y si el 20% era válido, lo que significa que la excepción fue lanzada y capturada el 80% del tiempo, la expresión regular era unas veinte veces más rápida que parseInt ().

Me sorprendió el resultado, considerando que el enfoque de expresiones regulares procesa cadenas válidas dos veces: una para la coincidencia y otra para parseInt (). Pero lanzar y atrapar excepciones más que compensó eso. Este tipo de situación no es probable que ocurra muy a menudo en el mundo real, pero si lo hace, definitivamente no debería usar la técnica de captura de excepciones. Pero si solo está validando la entrada del usuario o algo así, utilice el enfoque parseInt ().


He extendido las respuestas dadas por @Mecki y @incarnate , sin el relleno de stacktrace para Java.

Con Java 7+, podemos usar Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) . Pero para Java6, vea mi respuesta para esta pregunta.

// This one will regularly throw one public void method4(int i) throws NoStackTraceThrowable { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new NoStackTraceThrowable(); } } // This one will regularly throw one public void method5(int i) throws NoStackTraceRuntimeException { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new NoStackTraceRuntimeException(); } } public static void main(String[] args) { int i; long l; Test t = new Test(); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method4(i); } catch (NoStackTraceThrowable e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println( "method4 took " + l + " ms, result was " + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method5(i); } catch (RuntimeException e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println( "method5 took " + l + " ms, result was " + t.getValue() ); }

Salida con Java 1.6.0_45, en Core i7, 8GB de RAM:

method1 took 883 ms, result was 2 method2 took 882 ms, result was 2 method3 took 32270 ms, result was 2 // throws Exception method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

Por lo tanto, aún los métodos que devuelven valores son más rápidos, en comparación con los métodos que lanzan excepciones. En mi humilde opinión, no podemos diseñar una API clara simplemente utilizando tipos de retorno para flujos de error y éxito. Los métodos que lanzan excepciones sin stacktrace son 4-5 veces más rápidos que las excepciones normales.

Edición: NoStackTraceThrowable.java Gracias @Greg

public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }


He realizado algunas pruebas de rendimiento con JVM 1.5 y el uso de excepciones fue al menos 2 veces más lento. En promedio: el tiempo de ejecución en un método trivialmente pequeño se ha más que triplicado (3x) con excepciones. Un bucle trivialmente pequeño que tuvo que atrapar la excepción experimentó un aumento de 2 veces en el tiempo del self.

He visto números similares en código de producción, así como micro benchmarks.

Definitivamente, las excepciones NO se deben usar para nada que se llame con frecuencia. Lanzar miles de excepciones por segundo causaría un enorme cuello de botella.

Por ejemplo, usar "Integer.ParseInt (...)" para encontrar todos los valores incorrectos en un archivo de texto muy grande, muy mala idea. (He visto este método de utilidad que mata el rendimiento en el código de producción)

El uso de una excepción para informar un valor incorrecto en un formulario de GUI de usuario, probablemente no sea tan malo desde el punto de vista del rendimiento.

Sea o no una buena práctica de diseño, aceptaría la regla: si el error es normal / esperado, utilice un valor de retorno. Si es anormal, use una excepción. Por ejemplo: leyendo entradas de usuario, los valores incorrectos son normales; use un código de error. Al pasar un valor a una función de utilidad interna, los valores incorrectos deben filtrarse llamando al código; use una excepción.


HotSpot es bastante capaz de eliminar el código de excepción para las excepciones generadas por el sistema, siempre y cuando esté todo en línea. Sin embargo, la excepción creada explícitamente y las que de otro modo no se eliminan pasan mucho tiempo creando la traza de la pila. Reemplace fillInStackTrace para ver cómo esto puede afectar el rendimiento.


Incluso si lanzar una excepción no es lento, sigue siendo una mala idea lanzar excepciones para el flujo normal del programa. Usado de esta manera es análogo a un GOTO ...

Aunque supongo que eso no responde la pregunta. Me imagino que la sabiduría "convencional" de lanzar excepciones con lentitud era cierta en versiones anteriores de Java (<1.4). La creación de una excepción requiere que la máquina virtual cree todo el seguimiento de la pila. Mucho ha cambiado desde entonces en la máquina virtual para acelerar las cosas y esta es probablemente un área que ha mejorado.


Mi respuesta, desafortunadamente, es demasiado larga para publicar aquí. Así que permítanme resumir aquí y remitirle a http://www.fuwjax.com/how-slow-are-java-exceptions/ para obtener información detallada.

La verdadera pregunta aquí no es "¿Qué tan lentas son las fallas informadas como excepciones" en comparación con "el código que nunca falla"? como la respuesta aceptada puede hacerte creer. En su lugar, la pregunta debería ser "¿Qué tan lentas son las fallas reportadas como excepciones en comparación con las fallas reportadas de otra manera?" En general, las otras dos formas de informar fallas son con valores de centinela o con envoltorios de resultados.

Los valores de Sentinel son un intento de devolver una clase en caso de éxito y otra en caso de fracaso. Se puede pensar que es casi como devolver una excepción en lugar de lanzar una. Esto requiere una clase padre compartida con el objeto de éxito y luego hacer una comprobación de "instancia de" y un par de lanzamientos para obtener la información de éxito o fracaso.

Resulta que a riesgo de seguridad de tipo, los valores de Sentinel son más rápidos que las excepciones, pero solo por un factor de aproximadamente 2x. Ahora, eso puede parecer mucho, pero ese doble sólo cubre el costo de la diferencia de implementación. En la práctica, el factor es mucho menor, ya que nuestros métodos que pueden fallar son mucho más interesantes que algunos operadores aritméticos como en el código de ejemplo en otra parte de esta página.

Los envoltorios de resultados, por otro lado, no sacrifican la seguridad del tipo. Envuelven la información de éxito y fracaso en una sola clase. Entonces, en lugar de "instanceof", proporcionan un "isSuccess ()" y getters para los objetos de éxito y fracaso. Sin embargo, los objetos de resultados son aproximadamente 2 veces más lentos que el uso de excepciones. Resulta que crear un nuevo objeto contenedor cada vez es mucho más costoso que lanzar una excepción a veces.

Además de eso, las excepciones son el idioma suministrado para indicar que un método puede fallar. No hay otra manera de decir, solo a través de la API, qué métodos se espera que funcionen (en su mayoría) y cuáles reporten fallas.

Las excepciones son más seguras que los centinelas, más rápidas que los objetos de resultado y menos sorprendentes que cualquiera de ellas. No estoy sugiriendo que try / catch se reemplace si / else, pero las excepciones son la forma correcta de informar fallas, incluso en la lógica de negocios.

Dicho esto, me gustaría señalar que las dos formas más frecuentes de impactar sustancialmente el rendimiento que he encontrado son creando objetos innecesarios y bucles anidados. Si tiene la opción de crear una excepción o no crear una excepción, no cree la excepción. Si tiene la opción de crear una excepción a veces o crear otro objeto todo el tiempo, cree la excepción.


No sé si estos temas se relacionan, pero una vez quise implementar un truco basado en el seguimiento de la pila del hilo actual: quería descubrir el nombre del método, que activó la creación de instancias dentro de la clase instanciada (sí, la idea es una locura, Lo dejé totalmente). Así que descubrí que llamar a Thread.currentThread().getStackTrace() es extremadamente lento (debido al método nativo dumpThreads que usa internamente).

Por lo tanto, Java Throwable , en consecuencia, tiene un método nativo fillInStackTrace . Creo que el bloque killer- catch descrito anteriormente desencadena de alguna manera la ejecución de este método.

Pero déjame contarte otra historia ...

En Scala, algunas funciones funcionales se compilan en JVM utilizando ControlThrowable , que amplía Throwable y anula su fillInStackTrace de la siguiente manera:

override def fillInStackTrace(): Throwable = this

Así que adapté la prueba anterior (la cantidad de ciclos se reduce en diez, mi máquina es un poco más lenta :):

class ControlException extends ControlThrowable class T { var value = 0 def reset = { value = 0 } def method1(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0xfffffff) == 1000000000) { println("You''ll never see this!") } } def method2(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0xfffffff) == 1000000000) { throw new Exception() } } def method3(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0x1) == 1) { throw new Exception() } } def method4(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0x1) == 1) { throw new ControlException() } } } class Main { var l = System.currentTimeMillis val t = new T for (i <- 1 to 10000000) t.method1(i) l = System.currentTimeMillis - l println("method1 took " + l + " ms, result was " + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method2(i) } catch { case _ => println("You''ll never see this") } l = System.currentTimeMillis - l println("method2 took " + l + " ms, result was " + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method4(i) } catch { case _ => // do nothing } l = System.currentTimeMillis - l println("method4 took " + l + " ms, result was " + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method3(i) } catch { case _ => // do nothing } l = System.currentTimeMillis - l println("method3 took " + l + " ms, result was " + t.value) }

Entonces, los resultados son:

method1 took 146 ms, result was 2 method2 took 159 ms, result was 2 method4 took 1551 ms, result was 2 method3 took 42492 ms, result was 2

Usted ve, la única diferencia entre method4 y method4 es que lanzan diferentes tipos de excepciones. Yeap, method4 aún es más lento que method1 y method2 , pero la diferencia es mucho más aceptable.


Para su información, amplié el experimento que hizo Mecki:

method1 took 1733 ms, result was 2 method2 took 1248 ms, result was 2 method3 took 83997 ms, result was 2 method4 took 1692 ms, result was 2 method5 took 60946 ms, result was 2 method6 took 25746 ms, result was 2

Los primeros 3 son los mismos que los de Mecki (mi computadora portátil es obviamente más lenta).

method4 es idéntico a method3, excepto que crea un new Integer(1) lugar de throw new Exception() .

method5 es como method3, excepto que crea la new Exception() sin lanzarla.

method6 es como method3 excepto que lanza una excepción pre-creada (una variable de instancia) en lugar de crear una nueva.

En Java, gran parte del gasto de lanzar una excepción es el tiempo dedicado a recopilar el seguimiento de pila, que se produce cuando se crea el objeto de excepción. El costo real de lanzar la excepción, aunque grande, es considerablemente menor que el costo de crear la excepción.


¿Por qué las excepciones deberían ser más lentas de lo normal?

Mientras no imprima el seguimiento de pila en el terminal, guárdelo en un archivo o algo similar, el bloque de captura no hace más trabajo que otros bloques de código. Entonces, no puedo imaginar por qué "lanzar nuevo my_cool_error ()" debería ser tan lento.

¡Buena pregunta y espero más información sobre este tema!


Gran post sobre el rendimiento de excepción es:

https://shipilev.net/blog/2014/exceptional-performance/

La creación de instancias frente a la reutilización existente, con seguimiento de pila y sin, etc.

Benchmark Mode Samples Mean Mean error Units dynamicException avgt 25 1901.196 14.572 ns/op dynamicException_NoStack avgt 25 67.029 0.212 ns/op dynamicException_NoStack_UsedData avgt 25 68.952 0.441 ns/op dynamicException_NoStack_UsedStack avgt 25 137.329 1.039 ns/op dynamicException_UsedData avgt 25 1900.770 9.359 ns/op dynamicException_UsedStack avgt 25 20033.658 118.600 ns/op plain avgt 25 1.259 0.002 ns/op staticException avgt 25 1.510 0.001 ns/op staticException_NoStack avgt 25 1.514 0.003 ns/op staticException_NoStack_UsedData avgt 25 4.185 0.015 ns/op staticException_NoStack_UsedStack avgt 25 19.110 0.051 ns/op staticException_UsedData avgt 25 4.159 0.007 ns/op staticException_UsedStack avgt 25 25.144 0.186 ns/op

Dependiendo de la profundidad del seguimiento de la pila:

Benchmark Mode Samples Mean Mean error Units exception_0000 avgt 25 1959.068 30.783 ns/op exception_0001 avgt 25 1945.958 12.104 ns/op exception_0002 avgt 25 2063.575 47.708 ns/op exception_0004 avgt 25 2211.882 29.417 ns/op exception_0008 avgt 25 2472.729 57.336 ns/op exception_0016 avgt 25 2950.847 29.863 ns/op exception_0032 avgt 25 4416.548 50.340 ns/op exception_0064 avgt 25 6845.140 40.114 ns/op exception_0128 avgt 25 11774.758 54.299 ns/op exception_0256 avgt 25 21617.526 101.379 ns/op exception_0512 avgt 25 42780.434 144.594 ns/op exception_1024 avgt 25 82839.358 291.434 ns/op

Para otros detalles (incluido el ensamblador x64 de JIT), lea la publicación original del blog.

Eso significa que Hibernate / Spring / etc-EE-shit es lento debido a las excepciones (xD) y la reescritura del flujo de control de la aplicación lejos de las excepciones (reemplazarlo con continure/ breaky los booleanindicadores de retorno como en C desde la llamada al método) mejora el rendimiento de su aplicación 10x-100x , dependiendo de la frecuencia con la que los arrojes))


Mi opinión acerca de la velocidad de excepción frente a la verificación de datos mediante programación.

Muchas clases tenían convertidor de cadena a valor (escáner / analizador), bibliotecas respetadas y conocidas también;)

usualmente tiene forma

class Example { public static Example Parse(String input) throws AnyRuntimeParsigException ... }

el nombre de la excepción es solo un ejemplo, normalmente no está marcado (tiempo de ejecución), por lo que la declaración de tiros es solo mi foto

A veces existe una segunda forma:

public static Example Parse(String input, Example defaultValue)

nunca tirar

Cuando el segundo ins''t esté disponible (o el programador lea menos documentos y use solo primero), escriba dicho código con una expresión regular. Las expresiones regulares son geniales, políticamente correctas, etc.

Xxxxx.regex(".....pattern", src); if(ImTotallySure) { Example v = Example.Parse(src); }

Con este código los programadores no tienen costo de excepciones. PERO TIENE COSTO ALTAMENTE comparable de las expresiones regulares SIEMPRE versus el costo pequeño de la excepción a veces.

Yo uso casi siempre en ese contexto

try { parse } catch(ParsingException ) // concrete exception from javadoc { }

sin analizar stacktrace, etc, creo que después de las conferencias de Yours bastante velocidad.

No tengas miedo de las excepciones


Solo compare digamos Integer.parseInt con el siguiente método, que solo devuelve un valor predeterminado en el caso de datos no analizables en lugar de lanzar una excepción:

public static int parseUnsignedInt(String s, int defaultValue) { final int strLength = s.length(); if (strLength == 0) return defaultValue; int value = 0; for (int i=strLength-1; i>=0; i--) { int c = s.charAt(i); if (c > 47 && c < 58) { c -= 48; for (int j=strLength-i; j!=1; j--) c *= 10; value += c; } else { return defaultValue; } } return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value; }

Mientras aplique ambos métodos a datos "válidos", ambos funcionarán aproximadamente a la misma velocidad (incluso aunque Integer.parseInt se las arregla para manejar datos más complejos). Pero tan pronto como intente analizar datos no válidos (por ejemplo, analizar "abc" 1.000.000 veces), la diferencia en el rendimiento debería ser esencial.


Cambié la respuesta de @Mecki anterior para que method1 devuelva un valor booleano y una comprobación del método de llamada, ya que no puede reemplazar una Excepción por nada. Después de dos ejecuciones, method1 seguía siendo el más rápido o tan rápido como method2.

Aquí está la instantánea del código:

// Calculates without exception public boolean method1(int i) { value = ((value + i) / i) << 1; // Will never be true return ((i & 0xFFFFFFF) == 1000000000); } .... for (i = 1; i < 100000000; i++) { if (t.method1(i)) { System.out.println("Will never be true!"); } }

y resultados:

Ejecutar 1

method1 took 841 ms, result was 2 method2 took 841 ms, result was 2 method3 took 85058 ms, result was 2

Ejecutar 2

method1 took 821 ms, result was 2 method2 took 838 ms, result was 2 method3 took 85929 ms, result was 2