.net exception optimization performance

.net - Consideraciones de rendimiento para arrojar excepciones



exception optimization (8)

He encontrado el siguiente tipo de código muchas veces, y me pregunto si esta es una buena práctica (desde la perspectiva del rendimiento) o no:

try { ... // some code } catch (Exception ex) { ... // Do something throw new CustomException(ex); }

Básicamente, lo que el codificador está haciendo es que están abarcando la excepción en una excepción personalizada y lanzándola de nuevo.

¿Cómo se diferencia esto en el rendimiento de los siguientes dos:

try { ... // some code } catch (Exception ex) { .. // Do something throw ex; }

o

try { ... // some code } catch (Exception ex) { .. // Do something throw; }

Dejando de lado los argumentos de las mejores prácticas funcionales o de codificación, ¿hay alguna diferencia de rendimiento entre los 3 enfoques?


@Brad Tutterow

La excepción no se pierde en el primer caso, sino que se transfiere al constructor. Sin embargo, estoy de acuerdo con usted en el resto, el segundo enfoque es una muy mala idea debido a la pérdida del seguimiento de la pila. Cuando trabajé con .NET, me encontré con muchos casos en que otros programadores lo hacían, y me frustraba infinitamente cuando necesitaba ver la verdadera causa de una excepción, solo para descubrir que se volvía a sacar de un gran bloque de intentos donde Ahora no tengo idea de dónde se originó el problema.

También secundé el comentario de Brad de que no debes preocuparte por el rendimiento. Este tipo de micro optimización es una idea HORRIBLE. A menos que esté hablando de lanzar una excepción en cada iteración de un bucle for que se está ejecutando durante mucho tiempo, es muy probable que no se encuentre con problemas de rendimiento por el uso de su excepción.

Siempre optimice el rendimiento cuando tenga métricas que indiquen que NECESITA optimizar el rendimiento, y luego acceda a los puntos que hayan demostrado ser los culpables.

Es mucho mejor tener un código legible con capacidades de depuración fáciles (IE no oculta el seguimiento de la pila) en lugar de hacer que algo se ejecute un nanosegundo más rápido.

Una nota final sobre el ajuste de excepciones en una excepción personalizada ... esto puede ser una construcción muy útil, especialmente cuando se trata de UI. Puede envolver todos los casos excepcionales conocidos y razonables en alguna excepción personalizada básica (o una que se extienda desde dicha excepción base), y luego la IU simplemente puede detectar esta excepción base. Cuando se detecta, la excepción deberá proporcionar medios para mostrar información al usuario, digamos una propiedad de ReadableMessage, o algo similar. Por lo tanto, cada vez que la UI omita una excepción, es debido a un error que debe corregir, y cada vez que detecta una excepción, se trata de una condición de error conocida que la IU puede manejar y debe manejar adecuadamente.


Como David, supongo que el segundo y el tercero rinden mejor. Pero, ¿alguno de los tres tendría un rendimiento lo suficientemente bajo como para perder el tiempo preocupándose por ello? Creo que hay problemas más grandes que el rendimiento de los que preocuparse.

FxCop siempre recomienda el tercer enfoque sobre el segundo para que no se pierda el trazado original de la pila.

Editar: eliminó cosas que simplemente estaban mal y Mike tuvo la amabilidad de señalar.


El lanzamiento en su primer ejemplo tiene la sobrecarga de la creación de un nuevo objeto CustomException.

El re-lanzamiento en su segundo ejemplo lanzará una excepción de tipo Excepción.

El re-lanzamiento en su tercer ejemplo arrojará una excepción del mismo tipo que fue arrojado por su "código".

Entonces, el segundo y el tercer ejemplo usan menos recursos.


Obviamente incurres en la penalidad de crear objetos nuevos (la nueva Excepción) así que, exactamente como lo haces con cada línea de código que agregas a tu programa, debes decidir si la mejor categorización de excepciones paga por el trabajo adicional.

Como un consejo para tomar esa decisión, si sus nuevos objetos no llevan información adicional sobre la excepción, entonces puede olvidarse de construir nuevas excepciones.

Sin embargo, en otras circunstancias, tener una jerarquía de excepciones es muy conveniente para el usuario de sus clases. Supongamos que está implementando el patrón de fachada, ninguno de los escenarios considerados hasta ahora es bueno:

  1. no es bueno que levante cada excepción como un objeto Exception porque está perdiendo (probablemente) información valiosa
  2. no es bueno ni para levantar todo tipo de objetos que atrapes porque al hacerlo fallas en crear la fachada

En este caso hipotético, lo mejor que puede hacer es crear una jerarquía de clases de excepción que, al abstraer a los usuarios de las complejidades internas del sistema, les permita saber algo sobre el tipo de excepción producida.

Como nota al margen:

Personalmente no me gusta el uso de excepciones (jerarquías de clases derivadas de la clase Excepción) para implementar la lógica. Como en el caso:

try { // something that will raise an exception almost half the time } catch( InsufficientFunds e) { // Inform the customer is broke } catch( UnknownAccount e ) { // Ask for a new account number }


No hagas:

try { // some code } catch (Exception ex) { throw ex; }

Como esto perderá el rastro de la pila.

En lugar de hacer:

try { // some code } catch (Exception ex) { throw; }

Con solo tirar, solo necesita pasar la variable de excepción si desea que sea la excepción interna en una nueva excepción personalizada.


Espera ... ¿por qué nos preocupa el rendimiento si se lanza una excepción? A menos que usemos excepciones como parte del flujo de aplicaciones normal (que es WAYYYY en contra de las mejores prácticas).

Solo he visto los requisitos de rendimiento en lo que respecta al éxito, pero nunca con respecto al fracaso.


Desde un punto de vista de rendimiento puramente, supongo que el tercer caso es el más eficaz. Los otros dos necesitan extraer un seguimiento de pila y construir nuevos objetos, los cuales consumen bastante tiempo.

Una vez dicho esto, estos tres bloques de código tienen comportamientos muy diferentes (externos), por lo que compararlos es como preguntar si QuickSort es más eficiente que Agregar un elemento a un árbol rojo-negro. No es tan importante como seleccionar lo correcto para hacer.


Como otros han afirmado, el mejor rendimiento proviene del inferior, ya que solo estás volviendo a lanzar un objeto existente. El del medio es menos correcto porque pierde la pila.

Yo personalmente uso excepciones personalizadas si deseo desacoplar ciertas dependencias en el código. Por ejemplo, tengo un método que carga datos de un archivo XML. Esto puede salir mal de muchas maneras diferentes.

Podría no leer desde el disco (FileIOException), el usuario podría intentar acceder desde un lugar donde no están permitidos (SecurityException), el archivo podría estar dañado (XmlParseException), los datos podrían estar en el formato incorrecto (DeserialisationException).

En este caso, es más fácil para la clase que llama dar sentido a todo esto, todas estas excepciones vuelven a lanzar una única excepción personalizada (FileOperationException), lo que significa que la persona que llama no necesita referencias a System.IO o System.Xml, pero aún puede acceda a qué error ocurrió a través de una enumeración y cualquier información importante.

Como se dijo, no intente micro-optimizar algo como esto, el acto de lanzar una excepción es lo más lento que ocurre aquí. La mejor mejora es intentar evitar una excepción.

public bool Load(string filepath) { if (File.Exists(filepath)) //Avoid throwing by checking state { //Wrap anyways in case something changes between check and operation try { .... } catch (IOException ioFault) { .... } catch (OtherException otherFault) { .... } return true; //Inform caller of success } else { return false; } //Inform caller of failure due to state }