Respuestas:
No soy un experto en implementaciones de lenguaje (así que tómate esto con un grano de sal), pero creo que uno de los mayores costos es desenrollar la pila y almacenarla para el seguimiento de la pila. Sospecho que esto sucede solo cuando se lanza la excepción (pero no lo sé), y si es así, este sería un costo oculto de tamaño decente cada vez que se lanza una excepción ... así que no es como si estuvieras saltando de un solo lugar en el código a otro, están sucediendo muchas cosas.
No creo que sea un problema siempre y cuando esté utilizando excepciones para un comportamiento EXCEPCIONAL (por lo que no es su ruta típica y esperada a través del programa).
Tres puntos a destacar aquí:
En primer lugar, hay poca o NINGUNA penalización de rendimiento en tener bloques try-catch en su código. Esto no debe tenerse en cuenta al intentar evitar tenerlos en su aplicación. El impacto de rendimiento solo entra en juego cuando se lanza una excepción.
Cuando se lanza una excepción además de las operaciones de desenrollado de la pila, etc.que tienen lugar, lo que otros han mencionado, debe tener en cuenta que ocurre una gran cantidad de cosas relacionadas con el tiempo de ejecución / reflexión para completar los miembros de la clase de excepción, como el seguimiento de la pila. objeto y los diversos miembros de tipo, etc.
Creo que esta es una de las razones por las que el consejo general si va a volver a lanzar la excepción es simplemente en throw;
lugar de lanzar la excepción nuevamente o construir una nueva, ya que en esos casos toda la información de la pila se recopila mientras que en el simple tirarlo todo se conserva.
throw new Exception("Wrapping layer’s error", ex);
¿Está preguntando sobre la sobrecarga de usar try / catch / finalmente cuando no se lanzan excepciones, o la sobrecarga de usar excepciones para controlar el flujo del proceso? Esto último es algo parecido a usar una barra de dinamita para encender la vela de cumpleaños de un niño pequeño, y la sobrecarga asociada cae en las siguientes áreas:
Puede esperar errores de página adicionales debido a la excepción lanzada al acceder al código no residente y a los datos que normalmente no están en el conjunto de trabajo de su aplicación.
Ambos elementos anteriores suelen acceder a códigos y datos "fríos", por lo que es probable que se produzcan errores de página si tiene presión de memoria:
En cuanto al impacto real del costo, esto puede variar mucho dependiendo de qué más esté sucediendo en su código en ese momento. Jon Skeet tiene un buen resumen aquí , con algunos enlaces útiles. Tiendo a estar de acuerdo con su afirmación de que si llega al punto en que las excepciones están dañando significativamente su desempeño, tiene problemas en términos de su uso de las excepciones más allá del desempeño.
En mi experiencia, la mayor sobrecarga consiste en lanzar una excepción y manejarla. Una vez trabajé en un proyecto donde se usó un código similar al siguiente para verificar si alguien tenía derecho a editar algún objeto. Este método HasRight () se usó en todas partes de la capa de presentación y, a menudo, se solicitó para cientos de objetos.
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
Cuando la base de datos de prueba se llenó más con datos de prueba, esto condujo a una desaceleración muy visible al abrir nuevos formularios, etc.
Así que lo refactoricé a lo siguiente, que, de acuerdo con mediciones posteriores de quick 'n dirty, es aproximadamente 2 órdenes de magnitud más rápido:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
En resumen, usar excepciones en el flujo de proceso normal es aproximadamente dos órdenes de magnitud más lento que usar un flujo de proceso similar sin excepciones.
Contrariamente a las teorías comúnmente aceptadas, try
/ catch
puede tener implicaciones de rendimiento significativas, ¡y eso es si se lanza una excepción o no!
El primero ha sido cubierto en un par de publicaciones de blog por Microsoft MVP a lo largo de los años, y confío que pueda encontrarlos fácilmente, pero StackOverflow se preocupa tanto por el contenido, por lo que proporcionaré enlaces a algunos de ellos como evidencia de relleno :
try
/ catch
/finally
( y la segunda parte ), de Peter Ritchie explora las optimizaciones quetry
/catch
/finally
deshabilita (y profundizaré en esto con citas del estándar)Parse
frente a TryParse
frenteConvertTo
por Ian Huff declara abiertamente que "el manejo de excepciones es muy lento" y demuestra este punto por picadurasInt.Parse
yInt.TryParse
uno contra el otro ... Para cualquier persona que insiste en queTryParse
los usostry
/catch
detrás de las escenas, esto debería arrojar algo de luz!También existe esta respuesta que muestra la diferencia entre el código desensamblado con y sin usar try
/ catch
.
Parece tan obvio que no es una sobrecarga que es manifiestamente observables en la generación de código, y que los gastos generales, incluso parece ser reconocido por la gente que valora Microsoft! Sin embargo, estoy repitiendo Internet ...
Sí, hay docenas de instrucciones MSIL adicionales para una línea de código trivial, y eso ni siquiera cubre las optimizaciones deshabilitadas, por lo que técnicamente es una microoptimización.
Publiqué una respuesta hace años que se eliminó porque se enfocaba en la productividad de los programadores (la macro-optimización).
Esto es lamentable, ya que es probable que no se ahorren unos pocos nanosegundos aquí y allá del tiempo de la CPU para compensar muchas horas acumuladas de optimización manual por parte de los humanos. ¿Por qué paga más tu jefe: una hora de tu tiempo o una hora con la computadora encendida? ¿En qué momento desconectamos y admitimos que es hora de comprar una computadora más rápida ?
Claramente, deberíamos optimizar nuestras prioridades , ¡no solo nuestro código! En mi última respuesta, me basé en las diferencias entre dos fragmentos de código.
Usando try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Sin usar try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Considere desde la perspectiva de un desarrollador de mantenimiento, que es más probable que pierda su tiempo, si no en la creación de perfiles / optimización (mencionado anteriormente), lo que probablemente ni siquiera sería necesario si no fuera por el problema try
/ catch
, luego al desplazarse por código fuente ... ¡Uno de ellos tiene cuatro líneas adicionales de basura estándar!
A medida que se introducen más y más campos en una clase, toda esta basura repetitiva se acumula (tanto en el código fuente como en el código desensamblado) mucho más allá de los niveles razonables. Cuatro líneas extra por campo, y siempre son las mismas líneas ... ¿No nos enseñaron a evitar repetirnos? Supongo que podríamos ocultar el try
/ catch
detrás de alguna abstracción casera, pero ... entonces podríamos evitar las excepciones (es decir, el uso Int.TryParse
).
Este ni siquiera es un ejemplo complejo; He visto intentos de instanciar nuevas clases en try
/ catch
. Tenga en cuenta que todo el código dentro del constructor podría ser descalificado de ciertas optimizaciones que de otra manera serían aplicadas automáticamente por el compilador. ¿Qué mejor manera de dar lugar a la teoría de que el compilador es lento , en lugar de que el compilador está haciendo exactamente lo que se le dice que haga ?
Suponiendo que dicho constructor lanza una excepción y, como resultado, se activa algún error, el desarrollador de mantenimiento deficiente tiene que rastrearlo. Puede que no sea una tarea tan fácil, ya que, a diferencia del código espagueti de la pesadilla goto , try
/ catch
puede causar desorden en tres dimensiones , ya que podría subir en la pila no solo a otras partes del mismo método, sino también a otras clases y métodos. , todo lo cual será observado por el desarrollador de mantenimiento, ¡de la manera difícil ! Sin embargo, se nos dice que "goto es peligroso", ¡eh!
Al final menciono, try
/ catch
tiene su beneficio, que es que está diseñado para deshabilitar optimizaciones . ¡Es, por así decirlo, una ayuda para la depuración ! Para eso fue diseñado y es para lo que debería usarse como ...
Supongo que también es un punto positivo. Se puede usar para deshabilitar optimizaciones que de otro modo podrían paralizar los algoritmos de paso de mensajes seguros y cuerdos para aplicaciones multiproceso, y para detectar posibles condiciones de carrera;) Ese es el único escenario en el que puedo pensar para usar try / catch. Incluso eso tiene alternativas.
Qué hacer optimizaciones try
, catch
y finally
desactivar?
AKA
¿Cómo están try
, catch
y finally
útil como la depuración de las ayudas?
son barreras de escritura. Esto viene del estándar:
12.3.3.13 declaraciones Try-catch
Para una declaración stmt de la forma:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- El estado de asignación definida de v al comienzo del bloque try es el mismo que el estado de asignación definida de v al comienzo de stmt .
- El estado de asignación definido de v al comienzo de catch-block-i (para cualquier i ) es el mismo que el estado de asignación definido de v al comienzo de stmt .
- El estado de asignación definido de v en el punto final de stmt se asigna definitivamente si (y solo si) v se asigna definitivamente en el punto final del bloque try y cada bloque catch-i (para cada i de 1 an ).
En otras palabras, al comienzo de cada try
declaración:
try
declaración deben estar completas, lo que requiere un bloqueo de hilo para comenzar, ¡lo que lo hace útil para depurar condiciones de carrera!try
declaraciónUna historia similar es válida para cada catch
declaración; suponga que dentro de su try
declaración (o un constructor o función que invoca, etc.) asigna a esa variable que de otro modo no tendría sentido (digamos garbage=42;
), el compilador no puede eliminar esa declaración, sin importar cuán irrelevante sea para el comportamiento observable del programa . La asignación debe haberse completado antes de catch
ingresar al bloque.
Por lo que vale, finally
cuenta una historia igualmente degradante :
12.3.3.14 Declaraciones de prueba finalmente
Para una declaración de prueba stmt de la forma:
try try-block finally finally-block
• El estado de asignación definida de v al comienzo del bloque try es el mismo que el estado de asignación definida de v al comienzo de stmt .
• El estado de asignación definida de v al comienzo del bloque final es el mismo que el estado de asignación definida de v al comienzo de stmt .
• El estado de asignación definido de v en el punto final de stmt se asigna definitivamente si (y solo si): o v se asigna definitivamente en el punto final del bloque try o vse asigna definitivamente en el punto final del bloque final Si se realiza una transferencia de flujo de control (como una instrucción goto ) que comienza dentro del bloque try y termina fuera del bloque try , entonces v también se considera definitivamente asignado en ese controlar la transferencia de flujo si v se asigna definitivamente en el punto final del bloque final . (Esto no es un solo si, si v se asigna definitivamente por otra razón en esta transferencia de flujo de control, entonces todavía se considera definitivamente asignado).
12.3.3.15 declaraciones Try-catch-finalmente
Análisis de asignación definida para una declaración de prueba , captura y finalmente de la forma:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
se hace como si la declaración fuera una declaración try - finalmente adjuntando una declaración try - catch :
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
Sin mencionar que si está dentro de un método al que se llama con frecuencia, puede afectar el comportamiento general de la aplicación.
Por ejemplo, considero el uso de Int32.Parse como una mala práctica en la mayoría de los casos, ya que arroja excepciones para algo que se puede detectar fácilmente de otra manera.
Entonces, para concluir todo lo escrito aquí:
1) Use bloques try..catch para detectar errores inesperados, casi sin penalización de rendimiento.
2) No use excepciones para errores exceptuados si puede evitarlo.
Escribí un artículo sobre esto hace un tiempo porque había mucha gente preguntando sobre esto en ese momento. Puede encontrarlo y el código de prueba en http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx .
El resultado es que hay una pequeña cantidad de sobrecarga para un bloque try / catch, pero tan pequeña que debe ignorarse. Sin embargo, si está ejecutando bloques try / catch en bucles que se ejecutan millones de veces, es posible que desee considerar mover el bloque fuera del bucle si es posible.
El problema de rendimiento clave con los bloques try / catch es cuando realmente detecta una excepción. Esto puede agregar un retraso notable a su solicitud. Por supuesto, cuando las cosas van mal, la mayoría de los desarrolladores (y muchos usuarios) reconocen la pausa como una excepción que está a punto de suceder. La clave aquí es no utilizar el manejo de excepciones para operaciones normales. Como su nombre indica, son excepcionales y debes hacer todo lo posible para evitar que se lancen. No debe utilizarlos como parte del flujo esperado de un programa que funciona correctamente.
Hice una entrada de blog sobre este tema el año pasado. Echale un vistazo. La conclusión es que casi no hay costo por un bloque de prueba si no ocurre ninguna excepción, y en mi computadora portátil, una excepción fue de aproximadamente 36 μs. Eso podría ser menos de lo que esperaba, pero tenga en cuenta que esos resultados están en una pila poco profunda. Además, las primeras excepciones son realmente lentas.
try
/ catch
demasiado? Je, je), pero parece que está discutiendo con la especificación del idioma y algunos MVP de MS que también han escrito blogs sobre el tema, proporcionando medidas al contrario de tu consejo ... Estoy abierto a la sugerencia de que la investigación que he hecho es incorrecta, pero tendré que leer la entrada de tu blog para ver qué dice.
try-catch
bloque vs tryparse()
, pero el concepto es el mismo.
Es mucho más fácil escribir, depurar y mantener código libre de mensajes de error del compilador, mensajes de advertencia de análisis de código y excepciones aceptadas de rutina (en particular, excepciones que se lanzan en un lugar y se aceptan en otro). Debido a que es más fácil, el código estará, en promedio, mejor escrito y con menos errores.
Para mí, ese programador y la sobrecarga de calidad es el principal argumento en contra del uso de try-catch para el flujo del proceso.
La sobrecarga informática de las excepciones es insignificante en comparación y, por lo general, pequeña en términos de la capacidad de la aplicación para cumplir con los requisitos de rendimiento del mundo real.
Realmente me gusta la publicación del blog de Hafthor y, para agregar mis dos centavos a esta discusión, me gustaría decir que siempre ha sido fácil para mí hacer que DATA LAYER arroje solo un tipo de excepción (DataAccessException). De esta manera, mi BUSINESS LAYER sabe qué excepción esperar y la detecta. Luego, dependiendo de otras reglas comerciales (es decir, si mi objeto comercial participa en el flujo de trabajo, etc.), puedo lanzar una nueva excepción (BusinessObjectException) o continuar sin volver a lanzar.
Yo diría que no dude en usar try..catch siempre que sea necesario y utilícelo sabiamente.
Por ejemplo, este método participa en un flujo de trabajo ...
Comentarios
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
Podemos leer en Programming Languages Pragmatics de Michael L. Scott que los compiladores de hoy en día no agregan ninguna sobrecarga en caso común, es decir, cuando no ocurren excepciones. Entonces, cada trabajo se realiza en tiempo de compilación. Pero cuando se lanza una excepción en tiempo de ejecución, el compilador necesita realizar una búsqueda binaria para encontrar la excepción correcta y esto sucederá para cada nuevo lanzamiento que realice.
Pero las excepciones son excepciones y este costo es perfectamente aceptable. Si intenta realizar el manejo de excepciones sin excepciones y utiliza códigos de error de retorno en su lugar, probablemente necesitará una declaración if para cada subrutina y esto incurrirá en una sobrecarga en tiempo real. Usted sabe que una instrucción if se convierte en unas pocas instrucciones de ensamblaje, que se realizarán cada vez que ingrese en sus subrutinas.
Perdón por mi inglés, espero que te ayude. Esta información se basa en el libro citado; para obtener más información, consulte el Capítulo 8.5 Manejo de excepciones.
Analicemos uno de los mayores costos posibles de un bloque try / catch cuando se usa donde no debería ser necesario:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Y aquí está el que no tiene try / catch:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Sin contar el insignificante espacio en blanco, uno podría notar que estas dos piezas equivalentes de código tienen casi exactamente la misma longitud en bytes. Este último contiene 4 bytes menos sangría. ¿Eso es algo malo?
Para colmo de males, un estudiante decide hacer un bucle mientras la entrada se puede analizar como un int. La solución sin try / catch podría ser algo como:
while (int.TryParse(...))
{
...
}
Pero, ¿cómo se ve esto cuando se usa try / catch?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
Los bloques Try / catch son formas mágicas de desperdiciar la sangría, ¡y aún no sabemos la razón por la que falló! Imagínese cómo se siente la persona que realiza la depuración, cuando el código continúa ejecutándose más allá de una falla lógica grave, en lugar de detenerse con un error de excepción obvio y agradable. Los bloques de prueba / captura son la validación / saneamiento de datos de un hombre perezoso.
Uno de los costos menores es que los bloques try / catch deshabilitan ciertas optimizaciones: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx . Supongo que también es un punto positivo. Se puede usar para deshabilitar optimizaciones que de otro modo podrían paralizar los algoritmos de paso de mensajes sanos y seguros para aplicaciones multiproceso, y para detectar posibles condiciones de carrera;) Ese es el único escenario en el que puedo pensar para usar try / catch. Incluso eso tiene alternativas.
Int.Parse
a favor de Int.TryParse
.