2009-03-05 21 views
13

En primer lugar, un descargo de responsabilidad: Tengo experiencia en otros idiomas, pero todavía estoy aprendiendo las sutilezas de C#Bajo C# cuánto de un impacto en el rendimiento es un intento, lanzar y bloque catch

En al problema ... Estoy mirando un código, que utiliza los bloques de prueba/captura de una manera que me preocupa. Cuando una rutina de análisis se llama, en lugar de devolver un código de error, el programador utiliza la siguiente lógica

catch (TclException e) { 
    throw new TclRuntimeError("unexpected TclException: " + e.Message,e); 
} 

Esto es capturado por la persona que llama, que lanza el mismo error ...
... que se captura por la persona que llama, que arroja el mismo error ...
..... que es capturado por la persona que llama, que arroja el mismo error ...

copia de seguridad alrededor de 6 niveles.

¿Estoy en lo cierto al pensar que todos estos bloqueos de captura/lanzamiento están causando un problema de rendimiento o es una implementación razonable bajo C#?

+1

El bloque catch que se muestra aquí agrega información adicional al objeto de excepción, lo que puede justificar su presencia. Pero si el bloque catch es inútil, elimínalo. – Qwertie

Respuesta

10

Lanzar (en lugar de atrapar) es costoso.

No coloque un bloque de captura a menos que vaya a hacer algo útil (es decir, convertir a una excepción más útil, manejar el error).

Simplemente volviendo a lanzar la excepción (arrojar declaración sin argumento) o, peor aún, lanzar el mismo objeto que acaba de capturar es definitivamente lo incorrecto.

EDIT: Para evitar la ambigüedad:

volver a lanzar:

catch (SomeException) { 
    throw; 
} 

Crear excepción del anterior objeto de excepción, donde todo el tiempo de ejecución proporciona el estado (traza notablemente pila) se sobrescribe:

catch (SomeException e) { 
    throw e; 
} 

El último caso es una manera inútil de descartar información sobre la excepción. y sin nada anterior al lanzamiento en el bloque de captura es doblemente inútil. Puede ser peor:

catch (SomeException e) { 
    throw new SomeException(e.Message); 
} 

que pierde casi toda la información de estado útil que se incluye (incluido el contenido original).)

+0

¿Hay algo útil que incluya registrar el error antes de volver a lanzar, o sería mejor? cuidado en otro lugar (por ejemplo, en el Error de la aplicación)? – Nick

+0

Retirarse no es costoso, ya que no se debe crear ningún objeto Exception (que implica desenrollar la pila) –

+0

Pero en todos los niveles, ¿no están desenrollando la pila? – DevinB

14

Es un mal diseño en cualquier idioma.

Las excepciones están diseñadas para ser capturadas al nivel que usted puede manejar. Capturar una excepción, solo tirarla de nuevo es solo una pérdida de tiempo inútil (también hace que pierdas información valiosa sobre la ubicación del error original).

Aparentemente, el tipo que escribió ese código, solía usar códigos de error, y luego cambió a excepciones sin entender realmente cómo funcionan. Las excepciones "burbujean" automáticamente la pila si no hay captura en un nivel.

Además, tenga en cuenta que las excepciones son ocurrencias excepcionales. Cosa que debería nunca suceder. No deben usarse en lugar de la verificación de validez normal (es decir, no detectar una excepción de división por cero, verificar para ver si el divisor es cero de antemano).

+0

Ninguno de la información original se pierde en el ejemplo proporcionado en la pregunta, ya que la excepción original se pasa como el argumento innerException al constructor TclRuntimeError. – yfeldblum

+0

Eso es verdad.Sin embargo, la excepción real está enterrada a seis niveles de profundidad en el lado de la excepción lanzada en la cara de los usuarios. –

+2

En general, solo debe ajustar las excepciones en los puntos de abstracción, e incluso solo si proporcionará información adicional sobre el error. Por ejemplo, incluir un componente de terceros y ajustar las excepciones que son impares (IndexOutOfBounds-> KeyNotFound) – Guvante

1

Try/Catch/Throw are slow: una mejor implementación sería verificar el valor antes de atraparlo, pero si no puede continuar, es mejor tirar y atrapar solo cuando cuente. La comprobación y el registro son más eficientes de lo contrario.

+1

El try-block tiene poca o ninguna sobrecarga. Es el caso excepcional (lanzamiento/captura) que ralentiza el sistema. –

+0

Derecha - El punto estaba en su ejemplo, el uso de un try/catch fue más lento porque resultó en una excepción lanzada, que podría haberse evitado usando un control luego realizando otra acción en lugar de lanzar continuamente. – Fry

14

De acuerdo con msdn:Performance Tips and Tricks puede usar try y catch sin ningún problema de rendimiento hasta que ocurra el verdadero.

+2

Me has guardado muchos debates de un compañero de trabajo con ese enlace :) Aquí hay una cita directa del artículo, si no quieres molestarte en revisarlo: _ "Encontrar y diseñar el código de excepciones pesadas puede resulte en una ganancia de rendimiento decente. Tenga en cuenta que esto no tiene nada que ver con los bloques de prueba/captura: ** solo incurre en el costo cuando se lanza la excepción real. Puede utilizar tantos bloques de prueba/captura como desee. * * Usar excepciones de manera gratuita es cuando pierde rendimiento. Por ejemplo, debe mantenerse alejado de cosas como usar excepciones para el flujo de control. "_ –

+1

@kayleeFrye_onDeck Sí, puede usar try y catch sin ningún problema, tantas como sea posible. el único problema si el escenario throw se usa en el flujo regular del programa. – Avram

2

En general, lanzar una excepción es costoso en .NET. Simplemente tener un bloque try/catch/finally no lo es. Entonces, sí, el código existente es malo desde el punto de vista del rendimiento porque cuando arroja, arroja de 5 a 6 excepciones infladas sin agregar ningún valor a simplemente dejar que la excepción original naturalmente surja de 5 a 6 fotogramas de pila.

Peor aún, el código existente es realmente malo desde una perspectiva de diseño. Uno de los principales beneficios del manejo de excepciones (en comparación con los códigos de error de devolución) es que no necesita verificar excepciones/códigos de retorno en todas partes (en la pila de llamadas). Solo tiene que atraparlos en el pequeño número de lugares que realmente desee manejar ellos. Ignorar una excepción (a diferencia de ignorar un código de retorno) no ignora ni oculta el problema. Solo significa que se manejará más arriba en la pila de llamadas.

+0

Claramente, todo esto debe escribirse. Pero, como primer paso, ¿podría una mejora menor ser tan simple como cambiar la captura (TclException e) {throw e;} para atrapar (TclException e) {throw;} para permitir que la excepción suba a la pila? – Noah

+1

Sí, pero es incluso más fácil que eso. Simplemente elimine todo el bloque catch (TclException e). Si no captas la excepción, burbujeará la pila de llamadas por sí mismo. –

0

Si cada capa de la pila simplemente vuelve a generar el mismo tipo con la misma información, sin agregar nada nuevo, entonces es completamente absurdo.

Si estaba sucediendo en el límite entre bibliotecas desarrolladas independientemente, es comprensible. A veces, un autor de la biblioteca desea controlar qué excepciones escapan de su biblioteca, de modo que puedan cambiar su implementación más tarde sin tener que encontrar la manera de simular el comportamiento de lanzamiento de excepción de la versión anterior.

En general, es una mala idea atrapar y volver a tirar bajo ninguna circunstancia sin una buena razón. Esto se debe a que tan pronto como se encuentra un bloque catch, se ejecutan todos los bloques finalmente entre throw y catch. Esto solo debería suceder si se puede recuperar la excepción. Está bien en este caso porque se está detectando un tipo específico, por lo que el autor del código (con suerte) sabe que pueden deshacer con seguridad cualquiera de sus propios cambios de estado interno en respuesta a ese tipo de excepción específico.

Por lo tanto, esos bloques try/catch tienen un costo potencial en el momento del diseño: hacen que el programa sea más complicado. Pero en tiempo de ejecución solo impondrán un costo significativo cuando se lanza la excepción, porque la transmisión de la excepción a la pila se ha complicado.

-1

Las excepciones son lentas, intente no utilizarlas.

Ver la respuesta que di here.

Básicamente, Chris Brumme (que estaba en el equipo de CLR) dijo que están implementados como excepciones SEH, por lo que recibes un gran golpe cuando son lanzados, tienen que sufrir las penalizaciones mientras burbujean en la pila del sistema operativo. Es realmente un excellent article y profundiza en lo que sucede cuando lanzas una excepción. ej .:


Por supuesto, el mayor coste de excepciones es cuando realmente se lanza uno. Volveré a esto cerca del final del blog.


rendimiento. Las excepciones tienen un costo directo cuando realmente arroja y atrapa una excepción. También pueden tener un costo indirecto asociado con empujar controladores en la entrada del método. Y ellos a menudo pueden tener un costo insidioso por restringiendo las oportunidades de codegen.


Sin embargo, hay un problema de rendimiento a largo plazo serio con excepciones y esto debe tenerse en cuenta en su decisión .

considerar algunas de las cosas que suceden cuando se lanza una excepción:

  • Coge un seguimiento de la pila mediante la interpretación de metadatos emitida por el compilador para guiar nuestra desenredo de pila.

  • Ejecutar a través de una cadena de controladores de la pila, llamando a cada controlador dos veces.

  • compensar desajustes entre SEH, C++ y gestionados excepciones.

  • Asigne una instancia de excepción gestionada y ejecute su constructor. Es muy probable que esto implique buscar recursos para los diversos mensajes de error .

  • Probablemente haga un viaje a través del núcleo del sistema operativo. A menudo toma una excepción de hardware .

  • Notificar a cualquier depurador adjunto, perfiladores, controladores de excepción vectorial y otras partes interesadas.

Esto está a años luz de distancia de devolver un -1 de su función llamada. Las excepciones son intrínsecamente no locales, y si existe una tendencia obvia y duradera para las arquitecturas actuales , es que debe permanecer local para un buen rendimiento.

Algunas personas afirman que las excepciones no son un problema, no tienen problemas de rendimiento y en general son algo bueno. Estas personas obtienen muchos votos populares, pero simplemente están equivocados. He visto al personal de Microsoft hacer las mismas afirmaciones (por lo general, con los conocimientos técnicos generalmente reservados para el departamento de marketing), pero la conclusión de la boca del caballo es usarlos con moderación.

el viejo dicho, excepciones sólo deben utilizarse en circunstancias excepcionales, es tan cierto para C# como lo es para cualquier otro idioma.

+0

"Intentar no usarlos"? Esa es una tergiversación bastante burda del consejo de Brumme. –

+0

no, ese es mi consejo, y es una generalización para que la gente piense sobre lo que están haciendo. Chris dice que los use, pero con moderación: "... Asegurándose de que los casos de error son extremadamente raros" – gbjbaanb

+0

Por lo tanto, está sugiriendo que compruebe los valores en lugar de usar excepciones todos "quiera o no", por lo que en lugar de "Trate de no usar excepciones", ¿su consejo es más "Tratar de no crear excepciones"? – Fry

2

Eso no es terrible en sí mismo, pero uno realmente debería mirar el anidamiento.

El caso de uso aceptable es como tal:

Soy un componente de bajo-ish que pueden encontrarse con una serie de diferentes errores sin embargo mi consumidor sólo está interesado en un tipo específico de excepción. Por lo tanto puedo hacer esto:

catch(IOException ex) 
{ 
    throw new PlatformException("some additional context", ex); 
} 

Ahora bien, esto permite que el consumidor debe hacer:

try 
{ 
    component.TryThing(); 
} 
catch(PlatformException ex) 
{ 
    // handle error 
} 

Sí, sé que algunas personas dirán pero el consumidor debe coger el IOException pero esto depende de la forma abstracta del el código de consumo en realidad lo es. ¿Qué pasa si el Impl está guardando algo en el disco y el consumidor no tiene una razón válida para pensar que su operación tocará el disco? En tal escenario, no tendría sentido poner este manejo de excepciones en el código de consumo.

Lo que generalmente tratamos de evitar utilizando este patrón es colocar un manejador de excepción "catch-all" en el código de lógica de negocios porque queremos descubrir todos los posibles tipos de excepciones, ya que pueden conducir a un problema más fundamental eso necesita ser investigado Si no detectamos, se dispara, toca el controlador de nivel "superior superior" y debe detener la aplicación de ir más allá. Esto significa que el cliente informará esa excepción y tendrá la oportunidad de investigarla. Esto es importante cuando intenta construir un software robusto. Necesita encontrar todos estos casos de error y escribir un código específico para manejarlos.

Lo que no es muy bonito es anidar estas cantidades excesivas y ese es el problema que debe resolver con este código.

Como otro cartel declaró que las excepciones son para un comportamiento razonablemente excepcional, pero no lleve esto demasiado lejos. Básicamente, el código debe expresar la operación "normal" y las excepciones deben manejar los posibles problemas que pueda encontrar.

En términos de rendimiento, las excepciones son buenas, obtendrá resultados horribles si realiza una prueba con un depurador en un dispositivo incrustado pero en la versión sin un depurador, en realidad son bastante rápidas.

Lo principal que las personas olvidan al hablar sobre el rendimiento de las excepciones es que en los casos de error todo se ralentiza de todos modos porque el usuario ha encontrado un problema. ¿Realmente nos importa la velocidad cuando la red no funciona y el usuario no puede guardar su trabajo? Dudo mucho que el informe de error vuelva al usuario unos pocos ms más rápido va a hacer una diferencia.

La directriz principal para recordar cuando se discuten las excepciones es que Las excepciones no deben ocurrir dentro del flujo de aplicación normal (significado normal sin errores). Todo lo demás proviene de esa declaración.

En el ejemplo exacto que das, no estoy seguro. Me parece que no se obtiene realmente ningún beneficio al ajustar lo que parece ser una excepción tcl genérica en otra excepción de tcl que suena genérica. En todo caso, sugeriría buscar al creador original del código y saber si existe alguna lógica particular detrás de su pensamiento. Lo más probable es que puedas matar la captura.

+0

a menudo le importa el rendimiento: en un servidor, si lanza una excepción solo el 1% de las veces, pero recibe 100 llamadas por segundo, más de 100 usuarios, ¡eso es 100 excepciones por segundo!Eso puede ser un golpe de rendimiento serio a veces. – gbjbaanb

Cuestiones relacionadas