¿Cuáles son los efectos de las excepciones en el rendimiento en Java?

496

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

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

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

y

  1. es complicado porque la gente espera que solo se manejen los errores 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 de 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 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 lanzar excepciones sea inherentemente lento". Otra razón es que la generación de seguimiento de la pila es lo que la ralentiza.

Mi prueba (usando Java 1.6.0_07, Java HotSpot 10.0, en Linux de 32 bits), indica 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 regresar o tirar . De esta manera, el procesamiento real es el mismo. Intenté ejecutar los métodos en diferentes órdenes y promediar mis tiempos de prueba, pensando que podría haber sido el calentamiento de JVM. En todas mis pruebas, el lanzamiento fue al menos tan rápido como el retorno, si no más rápido (hasta 3.1% más rápido). Estoy completamente abierto a la posibilidad de que mis pruebas fueron incorrectas, pero no he visto nada en el camino del ejemplo de código, comparaciones de prueba o resultados en el último año o dos que muestren que el manejo de excepciones en Java sea realmente lento.

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

En el documento Manejo eficiente de excepciones de Java en la compilación justo a tiempo , los autores sugieren que la sola presencia de controladores de excepciones, incluso si no se lanzan excepciones, es suficiente para evitar que el compilador JIT optimice el código correctamente, lo que lo ralentiza . Todavía no he probado esta teoría.

John Ellinwood
fuente
8
Sé que no estaba preguntando sobre 2), pero realmente debería reconocer que usar una excepción para el flujo del programa no es mejor que usar GOTO. Algunas personas defienden gotos, algunas personas defenderían de lo que estás hablando, pero si le preguntas a alguien que ha implementado y mantenido por un período de tiempo, te dirán que ambas son prácticas de diseño difíciles de mantener (y probablemente maldecirán) el nombre de la persona que pensó que era lo suficientemente inteligente como para tomar la decisión de usarlos).
Bill K
80
Bill, afirmar que usar excepciones para el flujo del programa no es mejor que usar GOTO no es mejor que afirmar que usar condicionales y bucles para el flujo del programa no es mejor que usar GOTO. Es un arenque rojo. Explicate tú mismo. Las excepciones pueden y se usan de manera efectiva para el flujo del programa en otros idiomas. El código idiomático de Python usa excepciones regularmente, por ejemplo. Puedo y he mantenido código que usa excepciones de esta manera (no Java), y no creo que haya nada intrínsecamente malo en ello.
mmalone
14
@mmalone usando Excepciones para el flujo de control normal es una mala idea en Java porque la elección del paradigma se hizo de esa manera . Lea Bloch EJ2 - él dice claramente que, cita, (Artículo 57) exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow- dando una explicación completa y extensa de por qué. Y él era el tipo que escribió Java lib. Por lo tanto, él es quien define el contrato de API de las clases. Estoy de acuerdo con Bill K en este caso.
8
@ OndraŽižka Si algún marco lo hace (use Excepciones en condiciones no excepcionales), está defectuoso y roto por diseño, rompiendo el contrato de clase de Excepción del lenguaje. El hecho de que algunas personas escriban código pésimo no lo hace menos pésimo.
8
Nada menos que el creador de stackoverflow.com está equivocado acerca de las excepciones. La regla de oro del desarrollo de software nunca es hacer que lo simple sea complejo y difícil de manejar. Él escribe: "Es cierto que lo que debería ser un simple programa de 3 líneas a menudo florece a 48 líneas cuando se realiza una buena verificación de errores, pero así es la vida, ..." Esta es una búsqueda de pureza, no simplicidad.
sf_jeff

Respuestas:

345

Depende de cómo se implementen las excepciones. La forma más simple es usar setjmp y longjmp. Eso significa que todos los registros de la CPU se escriben en la pila (que ya lleva algo de tiempo) y posiblemente se deban crear otros datos ... todo esto ya sucede 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). Por lo tanto, 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 la mayoría de los casos (ya que todo se coloca en la pila que se limpia automáticamente si existe el método).

Sun y otros reconocieron que esto es posiblemente subóptimo y, por supuesto, las máquinas virtuales se vuelven cada vez más rápidas con el tiempo. Hay otra forma de implementar excepciones, lo que hace que el intento sea rápido como un rayo (en realidad, no pasa nada para intentarlo en general, todo lo que debe suceder ya está hecho cuando la clase es cargada por la VM) 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 posterior solo se ejecute en una JVM en un sistema específico? Dado que si alguna vez puede ejecutarse 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ácilmente posible en todos los sistemas. ¿Quieres permanecer 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 de prueba. 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 lanzamiento como un simple goto. No necesita guardar el estado de la pila ni necesita desenrollar la pila si se produce una excepción (solo necesita saltar a los controladores de captura). Sin embargo, esto no es lo que generalmente 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 usa el bloque try dentro de su método, ¿qué tipo de método será este, que no llame a ningún otro método? ¿Solo 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,

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 de prueba es demasiado pequeña para descartar factores de confusión como los procesos en segundo plano. ¡Pero el bloque de captura mató todo y lo hizo 66 veces más lento!

Como dije, el resultado no será tan malo si pones try / catch y arrojas todo dentro del mismo método (method3), pero esta es una optimización 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é estás tratando de hacer aquí, pero definitivamente hay una mejor manera de hacerlo que usando try / catch / throw.

Mecki
fuente
77
Gran respuesta, pero me gustaría agregar que, por lo que sé, System.nanoTime () debería usarse para medir el rendimiento, no System.currentTimeMillis ().
Simon Forsberg
10
@ SimonAndréForsberg nanoTime()requiere Java 1.5 y solo tenía Java 1.4 disponible en el sistema que utilicé para escribir el código anterior. Además, no juega un papel muy importante en la práctica. La única diferencia entre los dos es que uno es nanosegundos y el otro milisegundos y eso nanoTimeno está influenciado por las manipulaciones del reloj (que son irrelevantes, a menos que usted o el proceso del sistema modifique el reloj del sistema exactamente en el momento en que se ejecuta el código de prueba). En general, tiene razón, sin embargo, nanoTimees , por supuesto, la mejor opción.
Mecki
2
Realmente debe tenerse en cuenta que su prueba es un caso extremo. Muestra un impacto de rendimiento muy pequeño para el código con un trybloque, pero no throw. Su throwprueba arroja excepciones el 50% del tiempo que pasa por el try. Esa es claramente una situación en la que el fracaso no es excepcional . Reducir eso a solo un 10% reduce masivamente el impacto en el rendimiento. El problema con este tipo de prueba es que alienta a las personas a dejar de usar excepciones por completo. El uso de excepciones, para un manejo de casos excepcional, funciona mucho mejor de lo que muestra su prueba.
Nate
1
@Nate En primer lugar, dije muy claramente que todo esto depende de cómo se implementen las excepciones. Solo estaba probando UNA implementación específica, pero hay muchas y Oracle puede elegir una completamente diferente con cada lanzamiento. En segundo lugar, si las excepciones son solo excepcionales, lo que suelen ser, por supuesto, el impacto es menor, esto es tan obvio, que realmente no creo que haya que señalarlo explícitamente y, por lo tanto, no puedo entender su punto aquí en absoluto. Y en tercer lugar, el uso excesivo de la excepción es malo, todos están de acuerdo en eso, por lo que usarlos con mucho cuidado es algo muy bueno.
Mecki
44
@Glide Un lanzamiento no es como una limpieza return. Deja un método en algún lugar en el medio del cuerpo, tal vez incluso en el medio de una operación (que hasta ahora solo se ha completado en un 50%) y el catchbloque puede tener 20 cuadros de pila hacia arriba (un método tiene un trybloque, llamando al método1, que llama a method2, que llama a mehtod3, ..., y en method20 en medio de una operación se produce una excepción). La pila debe desenrollarse 20 cuadros hacia arriba, todas las operaciones sin terminar deben deshacerse (las operaciones no deben realizarse a la mitad) y los registros de la CPU deben estar limpios. Todo esto consume tiempo.
Mecki
256

FYI, extendí 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 en new Integer(1)lugar de hacer throw new Exception().

method5 es como method3 excepto que crea el new Exception()sin tirarlo.

method6 es como method3 excepto que arroja 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 la pila, que ocurre 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.

Hot Licks
fuente
49
+1 Su respuesta aborda el problema central: el tiempo necesario para desenrollar y rastrear la pila y, en segundo lugar, arrojar el error. Hubiera seleccionado esto como la respuesta final.
Ingeniero
99
bonito. ~ 70% creando la excepción, ~ 30% lanzándola. buena informacion
chaqke
1
@Basil: deberías poder deducirlo de los números anteriores.
Hot Licks
2
@HotLicks y esta es exactamente la razón por la cual es importante saber qué versión de Java se utilizó en la publicación
Thorbjørn Ravn Andersen
3
Podemos observar que en el código estándar, la creación y el lanzamiento de excepciones ocurre en casos raros (en tiempo de ejecución, quiero decir), si no es el caso, las condiciones de tiempo de ejecución son muy malas o el diseño es el problema en sí mismo; en ambos casos las actuaciones no son una preocupación ...
Jean-Baptiste Yunès
70

Aleksey Shipilëv hizo un análisis muy exhaustivo en el que compara las excepciones de Java en varias combinaciones de condiciones:

  • Excepciones recién creadas vs excepciones creadas previamente
  • Seguimiento de pila habilitado vs deshabilitado
  • Seguimiento de pila solicitado vs nunca solicitado
  • Atrapado en el nivel superior vs relanzado en cada nivel vs encadenado / envuelto en cada nivel
  • Varios niveles de profundidad de pila de llamadas Java
  • Sin optimizaciones de alineación vs alineación extrema vs configuración predeterminada
  • Campos definidos por el usuario leídos vs no leídos

También los compara con el desempeño 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 maravillosamente efectivas. Si los usa según lo diseñado, y solo comunica los casos verdaderamente excepcionales entre la abrumadora cantidad de casos no excepcionales manejados por 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 del seguimiento de la pila cuando se crea una instancia de Exception y el desenrollado de la pila durante el lanzamiento de la Excepción.

  3. Los costos de construcción de seguimiento de pila son proporcionales a la profundidad de pila en el momento de la instanciación 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 rastreo de pila y / o almacena en caché las excepciones, solo puede deshacerse de esta parte del costo de rendimiento.

  4. Los costos de eliminación de la pila dependen de la suerte que tengamos al acercar el controlador de excepciones en el código compilado. La estructuración cuidadosa del código para evitar la búsqueda de controladores de excepciones profundas probablemente nos esté ayudando 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 cuán hermoso suene, eso no significa que deba usar Excepciones como el flujo de control habitual, porque en ese caso está a merced de optimizar el compilador. Solo debe usarlos en casos verdaderamente excepcionales, donde la frecuencia de excepción amortiza el posible costo desafortunado de aumentar la excepción real.

  6. La regla general optimista parece ser una frecuencia de 10 ^ -4 para excepciones es suficientemente excepcional. Eso, por supuesto, depende de los pesos pesados ​​de las excepciones en sí, las acciones exactas tomadas en los manejadores de excepciones, etc.

El resultado es que cuando no se lanza una excepción, no paga un costo, por lo que cuando la condición excepcional es lo suficientemente rara, el manejo de excepciones es más rápido que usarlo ifcada vez. Vale la pena leer la publicación completa.

Doval
fuente
41

Mi respuesta, desafortunadamente, es demasiado larga para publicar aquí. Así que permítanme resumir aquí y remitirlos a http://www.fuwjax.com/how-slow-are-java-exceptions/ para los detalles arenosos.

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

Los valores centinela son un intento de devolver una clase en caso de éxito y otra en caso de fracaso. Puedes pensarlo casi como devolver una excepción en lugar de lanzar una. Esto requiere una clase primaria compartida con el objeto de éxito y luego hacer una comprobación de "instancia de" y un par de conversiones para obtener la información de éxito o fracaso.

Resulta que a riesgo de la 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 2x solo cubre el costo de la diferencia de implementación. En la práctica, el factor es mucho más bajo ya que nuestros métodos que pueden fallar son mucho más interesantes que algunos operadores aritméticos como en el código de muestra en otra parte de esta página.

Los Envoltorios de resultados, por otro lado, no sacrifican la seguridad de tipo en absoluto. Envuelven la información de éxito y fracaso en una sola clase. Entonces, en lugar de "instanceof", proporcionan un "isSuccess ()" y captadores para los objetos de éxito y error. Sin embargo, los objetos resultantes son aproximadamente 2 veces más lentos que usar 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 lenguaje proporcionado para indicar que un método puede fallar. No hay otra manera de saber, solo desde la API, qué métodos se espera que funcionen siempre (en su mayoría) y cuáles se espera que reporten fallas.

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

Dicho esto, me gustaría señalar que las dos formas más frecuentes de impactar sustancialmente el rendimiento que he encontrado son crear 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.

Fuwjax
fuente
55
Decidí probar el rendimiento a largo plazo de las tres implementaciones en comparación con una implementación de control que verifica la falla sin informar. El proceso tiene una tasa de falla de aproximadamente 4%. Una iteración de una prueba invoca el proceso 10000 veces contra una de las estrategias. Cada estrategia se prueba 1000 veces y las últimas 900 veces se utilizan para generar las estadísticas. Aquí están los tiempos promedio en nanos: Control 338 Excepción 429 Resultado 348 Sentinel 345
Fuwjax
2
Solo por diversión, deshabilité fillInStackTrace en la prueba de excepción. Aquí están los tiempos ahora: Control 347 Excepción 351 Resultado 364 Sentinel 355
Fuwjax
1
Fuwjax, a menos que me falte algo (y admito que solo leí tu publicación SO, no tu publicación de blog), parece que tus dos comentarios anteriores contradicen tu publicación. Supongo que los números más bajos son mejores en su punto de referencia, ¿verdad? En ese caso, generar excepciones con fillInStackTrace habilitado (que es el comportamiento predeterminado y habitual), resulta en un rendimiento más lento que las otras dos técnicas que describe. ¿Me estoy perdiendo algo o realmente comentaste para refutar tu publicación?
Felix GV
@Fuwjax: la forma de evitar la opción de "roca y lugar duro" que presenta aquí es preasignar un objeto que represente el "éxito". Por lo general, también se pueden preasignar objetos para los casos de falla comunes. Entonces, solo en el raro caso de devolver detalles adicionales, se crea un nuevo objeto. (Este es el equivalente OO de "códigos de error" enteros, más una llamada por separado para obtener los detalles del último error, una técnica que ha existido durante décadas)
ToolmakerSteve
@Fuwjax ¿Entonces lanzar una excepción no crea un objeto con su cuenta? No estoy seguro de entender ese razonamiento. Si lanza una excepción o devuelve un objeto de resultado, está creando objetos. En ese sentido, los objetos resultantes no son más lentos que lanzar una excepción.
Matthias
20

He ampliado 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 a 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, 8 GB 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

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

Editar: NoStackTraceThrowable.java Gracias @Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}
manikanta
fuente
Interesante, gracias. Aquí está la declaración de clase faltante:public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
Greg
al principio ¿Escribiste With Java 7+, we can usepero luego escribiste, Output with Java 1.6.0_45,así que este es el resultado de Java 6 o 7?
WBAR
1
@WBAR de Java 7, solo necesitamos usar el Throwableconstructor que tiene boolean writableStackTracearg. Pero eso no está presente en Java 6 y versiones posteriores. Es por eso que he dado una implementación personalizada para Java 6 y versiones posteriores. Entonces, el código anterior es para Java 6 y siguientes. Lea atentamente la primera línea del segundo párrafo.
manikanta
@manikanta "En mi humilde opinión, no podemos diseñar una API clara simplemente usando los tipos de retorno para los flujos de éxito y error".
Hejazzman
@Hejazzman, estoy de acuerdo. Pero Optionalo algo similar llegó un poco tarde a Java. Antes de eso también utilizamos objetos de contenedor con banderas de éxito / error. Pero parece ser un poco pirateado y no me parece natural.
manikanta
8

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 () solo 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 intervenir el bucle), 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 () era 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 fue aproximadamente veinte veces más rápida que parseInt ().

Me sorprendió el resultado, considerando que el enfoque regex procesa cadenas válidas dos veces: una para el partido y otra vez para parseInt (). Pero lanzar y atrapar excepciones más que compensó eso. No es probable que este tipo de situación ocurra con mucha frecuencia en el mundo real, pero si lo hace, definitivamente no debe usar la técnica de captura de excepciones. Pero si solo está validando la entrada del usuario o algo así, utilice el enfoque parseInt ().

Alan Moore
fuente
¿Qué JVM usaste? ¿Sigue siendo tan lento con sun-jdk 6?
Benedikt Waldvogel
Lo desenterré y lo volví a ejecutar bajo JDK 1.6u10 antes de enviar esa respuesta, y esos son los resultados que publiqué.
Alan Moore
¡Esto es muy, muy útil! Gracias. Para mis casos de uso habituales, necesito analizar las entradas del usuario (usando algo como Integer.ParseInt()) y espero que la mayoría de las veces la entrada del usuario sea correcta, por lo que para mi caso de uso parece que tomar el golpe de excepción ocasional es el camino a seguir. .
markvgti
8

Creo que el primer artículo se refiere al acto de atravesar la pila de llamadas y crear un seguimiento de la pila como la parte costosa, y aunque el segundo artículo no lo dice, creo que esa 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 trazas de pila, etc.)

Pero aún así, creo que esto debería considerarse solo un mal necesario, un ú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 acostumbrarse a usar excepciones para el flujo de control. ¡Especialmente no por razones de rendimiento! Como usted mismo menciona en el n. ° 2, corre el riesgo de enmascarar errores graves en su código de esta manera, y será más difícil de mantener para los nuevos programadores.

Los microbenchmarks en Java son sorprendentemente difíciles de acertar (me han dicho), especialmente cuando entras en territorio JIT, por lo que realmente dudo que usar excepciones sea más rápido que el "retorno" en la vida real. Por ejemplo, sospecho que tienes entre 2 y 5 cuadros de pila en tu prueba. Ahora imagine que su código será invocado por un componente JSF implementado por JBoss. Ahora puede tener un seguimiento de pila que tiene varias páginas.

¿Quizás podrías publicar tu código de prueba?

Lars Westergren
fuente
7

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 desencadenó la creación de instancias dentro de la clase instanciada (sí, la idea es una locura, Lo dejé totalmente). Entonces descubrí que las llamadas Thread.currentThread().getStackTrace()son extremadamente lentas (debido al dumpThreadsmétodo nativo que usa internamente).

Entonces Throwable, Java , correspondientemente, tiene un método nativo fillInStackTrace. Creo que el catchbloque asesino descrito anteriormente de alguna manera desencadena la ejecución de este método.

Pero déjame contarte otra historia ...

En Scala, algunas características funcionales se compilan en JVM utilizando ControlThrowable, lo que extiende Throwabley anula su fillInStackTracede la siguiente manera:

override def fillInStackTrace(): Throwable = this

Así que adapté la prueba anterior (la cantidad de ciclos disminuye 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

Verá, la única diferencia entre method3y method4es que arrojan diferentes tipos de excepciones. Yeap, method4sigue siendo más lento que method1y method2, pero la diferencia es mucho más aceptable.

encarnar
fuente
6

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 más que triplicado (3x) con excepciones. Un ciclo trivialmente pequeño que tuvo que atrapar la excepción vio un aumento de 2 veces en el tiempo libre.

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

Las excepciones definitivamente NO deben usar para algo que se llama 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 matar el rendimiento en el código de producción)

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

Si es o no una buena práctica de diseño, seguiría la regla: si el error es normal / esperado, entonces use un valor de retorno. Si es anormal, use una excepción. Por ejemplo: al leer las entradas del 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 se deben filtrar llamando al código; use una excepción.

James Schek
fuente
Permítame sugerirle algunas cosas que SON BUENAS para hacer: si necesita un número en un formulario, en lugar de usar Integer.valueOf (String), debería considerar usar un comparador de expresiones regulares. Puede precompilar y reutilizar el patrón, por lo que la creación de coincidencias es barata. Sin embargo, en un formulario GUI, tener un isValid / validate / checkField o lo que tenga probablemente sea más claro. Además, con Java 8 tenemos mónadas opcionales, así que considere usarlas. (la respuesta es de 9 años, pero aún así: p)
Haakon Løtveit
4

El rendimiento de excepción en Java y C # deja mucho que desear.

Como programadores, esto nos obliga a vivir según la regla "las excepciones deben ser infrecuentes", simplemente por razones prácticas de rendimiento.

Sin embargo, como informáticos, debemos rebelarnos contra este estado problemático. La persona que crea una función a menudo no tiene idea de con qué frecuencia se llamará o si es más probable que tenga éxito o fracase. Solo la persona que llama tiene esta información. Intentar evitar excepciones conduce a idoms de API poco claros donde en algunos casos solo tenemos versiones de excepción limpias pero lentas, y en otros casos tenemos errores de valor de retorno rápidos pero torpes, y en otros casos terminamos con ambos . El implementador de la biblioteca puede tener que escribir y mantener dos versiones de API, y la persona que llama tiene que decidir cuál de las dos versiones usar en cada situación.

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

Realmente me gustaría ver mecanismos de excepción implementados utilizando técnicas más cercanas a los valores de retorno, para que podamos tener un rendimiento más cercano a los valores de retorno ... ya que esto es a lo que volvemos en el código sensible al rendimiento.

Aquí hay una muestra de código que compara el rendimiento de excepción con el rendimiento de valor de retorno de error.

TestIt clase pública {

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

Verificar y propagar los valores de retorno agrega algún costo en comparación con la llamada nula de línea de base, y ese costo es proporcional a la profundidad de la llamada. A una profundidad de cadena de llamada de 8, la versión de verificación del valor de retorno de error fue aproximadamente un 27% más lenta que la versión de línea de base que no verificó los valores de retorno.

El rendimiento de excepción, en comparación, no es una función de la profundidad de la llamada, sino de la frecuencia de excepción. Sin embargo, la degradación a medida que aumenta la frecuencia de excepción es mucho más dramática. Con solo una frecuencia de error del 25%, 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 están haciendo las compensaciones incorrectas en nuestras implementaciones de excepción. Las excepciones podrían ser más rápidas, ya sea evitando los costosos pasos de acecho o simplemente convirtiéndolos en una verificación de valor de retorno compatible con el compilador. Hasta que lo hagan, estamos atrapados evitándolos cuando queremos que nuestro código se ejecute rápido.

David Jeske
fuente
3

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

Tom Hawtin - tackline
fuente
2

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

Sin embargo, supongo que eso realmente no responde la pregunta. Me imagino que la sabiduría "convencional" de lanzar excepciones siendo lenta era cierta en versiones anteriores de Java (<1.4). Crear una excepción requiere que la máquina virtual cree todo el seguimiento de la pila. Mucho ha cambiado desde entonces en la VM para acelerar las cosas y este es probablemente un área que se ha mejorado.

usuario38051
fuente
1
Sería bueno definir "flujo de programa normal". Mucho se ha escrito sobre el uso de excepciones comprobadas como una falla del proceso comercial y una excepción no verificada para fallas no recuperables, por lo que, en cierto sentido, una falla en la lógica comercial aún podría considerarse como un flujo normal.
Spencer Kormos
2
@Spencer K: Una excepción, como su nombre lo indica, significa que se descubrió una situación excepcional (un archivo desapareció, una red se cerró repentinamente, ...). Esto implica que la situación fue INESPERADA. Si se ESPERA que ocurra la situación, no usaría una excepción para ello.
Mecki
2
@Mecki: correcto. Recientemente tuve una discusión con alguien sobre esto ... Ellos estaban escribiendo un marco de Validación y estaban lanzando una excepción en caso de falla de validación. Creo que esta es una mala idea, ya que sería bastante común. Prefiero ver que el método devuelva un ValidationResult.
user38051
2
En términos de flujo de control, una excepción es análoga a a breako return, no a goto.
Hot Licks
3
Hay toneladas de paradigmas de programación. No puede haber un solo "flujo normal", sea lo que sea lo que quiera decir con eso. Básicamente, el mecanismo de excepción es solo una forma de abandonar rápidamente el marco actual y desenrollar la pila hasta cierto punto. La palabra "excepción" no implica nada sobre su naturaleza "inesperada". Un ejemplo rápido: es muy natural "tirar" 404s de aplicaciones web cuando ocurren ciertas circunstancias en el camino. ¿Por qué no se implementaría esa lógica con excepciones? ¿Cuál es el antipatrón?
Encarnado el
2

Simplemente 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 generar 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;
  }

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

inflamador
fuente
2

La gran publicación sobre el rendimiento de excepción es:

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

Instanciar vs reutilizar existentes, 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 son lentos debido a las excepciones (xD) y reescribir el flujo de control de la aplicación lejos de las excepciones (reemplazarlo con continure/ breaky devolver booleanindicadores como en C desde la llamada al método) mejora el rendimiento de su aplicación 10x-100x , dependiendo de con qué frecuencia los arrojes))

gavenkoa
fuente
0

Cambié la respuesta de @Mecki anterior para que el método1 devuelva un booleano y una verificación en el método de llamada, ya que no puede reemplazar una Excepción con nada. Después de dos ejecuciones, el método1 seguía siendo el más rápido o el más rápido que el método2.

Aquí hay una 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:

Correr 1

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

Correr 2

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

Una excepción es para manejar condiciones inesperadas en tiempo de ejecución solamente.

El uso de una excepción en lugar de una validación simple que se puede hacer en tiempo de compilación retrasará la validación hasta el tiempo de ejecución. Esto a su vez reducirá la eficiencia del programa.

Lanzar una excepción en lugar de usar una simple validación if..else también hará que el código sea complejo de escribir y mantener.

Gopinath
fuente
-3

Mi opinión sobre la velocidad de excepción versus 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;)

generalmente tiene forma

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

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

a veces existe una segunda forma:

public static Example Parse(String input, Example defaultValue)

nunca tirando

Cuando el segundo no esté disponible (o el programador lea menos documentos y use solo el primero), escriba dicho código con 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 el costo de las excepciones. PERO TIENE un costo muy alto comparable de expresiones regulares SIEMPRE versus un pequeño costo de excepción a veces.

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 rápido.

No tengas miedo Excepciones

Jacek Cz
fuente
-5

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

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

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

Qualbeen
fuente
17
La excepción tiene que capturar la información sobre el seguimiento de la pila, incluso si en realidad no se utiliza.
Jon Skeet