what java exception-handling if-statement try-catch overhead

what - exception hierarchy in java



Java si vs. try/catch overhead (5)

¿Hay alguna sobrecarga en Java para usar un bloque try / catch , en oposición a un bloque if (suponiendo que el código adjunto no lo solicita)?

Por ejemplo, tome las siguientes dos implementaciones simples de un método de "ajuste seguro" para cadenas:

public String tryTrim(String raw) { try { return raw.trim(); } catch (Exception e) { } return null; } public String ifTrim(String raw) { if (raw == null) { return null; } return raw.trim(); }

Si la entrada en raw rara vez es null , ¿hay alguna diferencia de rendimiento entre los dos métodos?

Además, ¿ es un buen patrón de programación utilizar el enfoque tryTrim() para simplificar el diseño del código, especialmente cuando se pueden evitar muchos bloques que comprueban las condiciones de error raro encerrando el código en un bloque try / catch?

Por ejemplo, es un caso común tener un método con N parameters , que usa M <= N de ellos cerca de su inicio, fallando rápidamente y de manera determinista si dicho parámetro es "inválido" (por ejemplo, una cadena vacía o nula), sin afectar el resto del código.

En tales casos, en lugar de tener que escribir k * M si los bloques (donde k es el número promedio de verificaciones por parámetro, p. Ej. k = 2 para cadenas vacías o nulas), un bloque try / catch acortaría significativamente el código y 1 -2 línea de comentario podría utilizarse para señalar explícitamente la lógica "no convencional".

Tal patrón también aceleraría el método, especialmente si las condiciones de error ocurren raramente, y lo haría sin comprometer la seguridad del programa (asumiendo que las condiciones de error son "normales", por ejemplo, como en un método de procesamiento de cadena donde valores nulos o vacíos son aceptables, aunque raramente en presencia).


De lo contrario, las excepciones son rápidas de lanzar y atrapar (aunque un if es probablemente aún más rápido), pero lo lento está creando la traza de la pila de la excepción, porque necesita recorrer toda la pila actual. (En general, es malo usar excepciones para el flujo de control, pero cuando eso realmente se necesita y las excepciones deben ser rápidas, es posible omitir la creación del rastreo de pila anulando el método Throwable.fillInStackTrace() , o para guardar una instancia de excepción y lanzarlo repetidamente en lugar de crear siempre una nueva instancia de excepción).


En lo que respecta a los gastos generales, puede probar usted mismo:

public class Overhead { public static void main(String[] args) { String testString = ""; long startTimeTry = System.nanoTime(); tryTrim(testString); long estimatedTimeTry = System.nanoTime() - startTimeTry; long startTimeIf = System.nanoTime(); ifTrim(testString); long estimatedTimeIf = System.nanoTime() - startTimeIf; System.out.println("Try time:" + estimatedTimeTry); System.out.println("If time:" + estimatedTimeIf); } public static String tryTrim(String raw) { try { return raw.trim(); } catch (Exception e) { } return null; } public static String ifTrim(String raw) { if (raw == null) { return null; } return raw.trim(); }

}

Los números que obtuve son:

Try time:8102 If time:1956

En cuanto a qué estilo, es una pregunta por separado. La sentencia if parece bastante natural, pero la prueba parece realmente extraña por varias razones: - capturaste Exception aunque estés buscando valor NULL, ¿esperas que ocurra algo "excepcional" (de lo contrario, capturar NullException)? - Atrapaste esa Excepción, ¿vas a reportarla o tragar? etc. etc. etc.

Editar: vea mi comentario sobre por qué esta es una prueba inválida, pero realmente no quería dejar esto parado aquí. Simplemente intercambiando tryTrim y ifTrim, de repente obtenemos los siguientes resultados (en mi máquina):

Try time:2043 If time:6810

En lugar de comenzar a explicar todo esto, solo lea this para el comienzo: Cliff también tiene algunas diapositivas geniales sobre todo el tema, pero no puedo encontrar el enlace en este momento.

Saber cómo funciona la excepción en Hotspot, estoy bastante seguro de que en una prueba correcta try / catch sin excepción) sería el rendimiento base (porque no hay gastos generales), pero el JIT puede jugar algunos trucos con las comprobaciones de Nullpointer seguidas por invocaciones de método (sin comprobación explícita, pero atrapa la excepción hw si el objeto es nulo) en cuyo caso obtendríamos el mismo resultado. Además, no lo olvide: ¡estamos hablando de la diferencia de uno fácilmente predecible si sería UN ciclo de CPU! La llamada de recorte costará un millón de veces eso.


Esta pregunta casi ha sido "respondida hasta la muerte", pero creo que hay algunos puntos más que podrían ser útiles:

  • El uso de try / catch para un flujo de control no excepcional es un mal estilo (en Java). (A menudo hay debate sobre lo que significa "no excepcional" ... pero ese es un tema diferente).

  • Parte de la razón por la cual es un estilo malo es que try / catch es mucho más caro que una declaración de flujo de control regular 1 . La diferencia real depende del programa y la plataforma, pero espero que sea 1000 o más veces más costoso. Entre otras cosas, la creación del objeto de excepción captura un seguimiento de pila, buscando y copiando información sobre cada cuadro en la pila. Cuanto más profunda es la pila, más necesita ser copiada.

  • Otra parte de la razón por la cual es un estilo malo es que el código es más difícil de leer.

1 - El JIT en versiones recientes de Java 7 puede optimizar el manejo de excepciones para reducir drásticamente los gastos generales, en algunos casos. Sin embargo, estas optimizaciones no están habilitadas por defecto.

También hay problemas con la forma en que ha escrito el ejemplo:

  • La Exception captura es una práctica muy mala, porque existe la posibilidad de que atrape otras excepciones no verificadas por accidente. Por ejemplo, si lo hiciera con una llamada a raw.substring(1) , también podría detectar posibles StringIndexOutOfBoundsException s ... y ocultar errores.

  • Lo que intenta hacer tu ejemplo es (probablemente) el resultado de una práctica deficiente al tratar con cadenas null . Como principio general, debe intentar minimizar el uso de cadenas null e intentar limitar su propagación (intencional). Siempre que sea posible, use una cadena vacía en lugar de null para indicar "sin valor". Y cuando tenga un caso donde necesite pasar o devolver una cadena vacía, documente claramente en su método javadocs. Si sus métodos se llaman con un null cuando no deberían ... es un error. Deja que arroje una excepción. No intente compensar el error por (en este ejemplo) devolviendo null .

SEGUIR

Mi pregunta era más general, no solo para valores nulos.

... y la mayoría de los puntos en mi respuesta no son sobre valores nulos!

Pero tenga en cuenta que hay muchos casos en los que desea permitir el valor nulo ocasional, o cualquier otro valor que pueda producir una excepción, y simplemente ignórelos. Este es el caso, por ejemplo, al leer valores de clave / par de algún lugar y pasarlos a un método como el tryTrim () anterior.

Sí, hay situaciones en null que se esperan valores null , y debe lidiar con ellos.

Pero yo diría que lo que tryTrim() está haciendo es (típicamente) la forma incorrecta de lidiar con null . Compara estos tres bits de código:

// Version 1 String param = httpRequest.getParameter("foo"); String trimmed = tryTrim(param); if (trimmed == null) { // deal with case of ''foo'' parameter absent } else { // deal with case of ''foo'' parameter present } // Version 2 String param = httpRequest.getParameter("foo"); if (param == null) { // deal with case of ''foo'' parameter absent } else { String trimmed = param.trim(); // deal with case of ''foo'' parameter present } // Version 3 String param = httpRequest.getParameter("foo"); if (param == null) { // treat missing and empty parameters the same param = ""; } String trimmed = param.trim();

En definitiva, tiene que tratar el null diferente a una cadena normal, y por lo general es una buena idea hacerlo lo antes posible. Cuanto más se permita que el null se propague desde su origen de punto, más probable es que el programador olvide que un valor null es una posibilidad, y escriba un código con errores que asuma un valor no nulo. Y olvidar que un parámetro de solicitud HTTP podría faltar (es decir, param == null ) es un caso clásico en el que esto sucede.

No estoy diciendo que tryTrim() sea ​​intrínsecamente malo. Pero el hecho de que un programador sienta la necesidad de escribir métodos como este es probablemente indicativo de un manejo nulo menos que ideal.


Sé que estás preguntando sobre el rendimiento general, pero realmente no deberías usar try / catch y if intercambiable.

try / catch es para cosas que salen mal que están fuera de su control y no en el flujo normal del programa. Por ejemplo, ¿intenta escribir en un archivo y el sistema de archivos está lleno? Esa situación normalmente debería manejarse con try / catch .

if declaraciones deben ser flujo normal y verificación de errores ordinarios. Entonces, por ejemplo, ¿el usuario no puede llenar un campo de entrada requerido? Úselo para eso, no try / catch .

Me parece que su código de ejemplo sugiere fuertemente que el enfoque correcto es una declaración if y no una try / catch .

Para responder a su pregunta, supongo que generalmente hay más sobrecarga en un try / catch que en un if . Para estar seguro, obtenga un generador de perfiles de Java y descubra el código específico que le interesa. Es posible que la respuesta pueda variar según la situación.


Usa la segunda versión. Nunca use excepciones para el flujo de control cuando haya otras alternativas disponibles, ya que eso no es para lo que están ahí. Las excepciones son por circunstancias excepcionales.

Si bien sobre el tema, no atrape la Exception aquí, y especialmente no la trague. En su caso, esperaría una NullPointerException . Si capturaras algo, eso es lo que atraparías ( pero vuelve al párrafo uno, no hagas esto ). Cuando capturas (¡y tragas!) Exception , estás diciendo "no importa lo que vaya mal, puedo manejarlo. No me importa de qué se trata". ¡Tu programa podría estar en un estado irrevocable! Solo capte aquello para lo que esté preparado, permita que todo lo demás se propague a una capa que pueda manejarlo, incluso si esa capa es la capa superior y todo lo que hace es registrar la excepción y luego presionar el interruptor de expulsión.