¿Cuánto más caro es una excepción que un valor de retorno?

Es posible cambiar este código, con un valor de retorno y una excepción:

public Foo Bar(Bar b) { if(b.Success) { return b; } else { throw n.Exception; } } 

A esto, lo que arroja excepciones separadas para el éxito y el fracaso.

 public Foo Bar(Bar b) { throw b.Success ? new BarException(b) : new FooException(); } try { Bar(b) } catch(BarException bex) { return ex.Bar; } catch(FooException fex) { Console.WriteLine(fex.Message); } 

Lanzar una excepción es definitivamente más costoso que devolver un valor. Pero en términos de costo bruto es difícil decir cuánto más costosa es una excepción.

Al decidir sobre un valor de retorno frente a una excepción, siempre debe considerar la siguiente regla.

Solo usar excepciones para circunstancias excepcionales.

Nunca deben usarse para control general.

Usando el código a continuación, las pruebas revelaron que la llamada + retorno sin excepciones tomó alrededor de 1.6 microsegundos por iteración, mientras que las excepciones (lanzar más captura) agregaron aproximadamente 4000 microsegundos cada una. (!)

 public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { DateTime start = DateTime.Now; bool PreCheck = chkPrecheck.Checked; bool NoThrow = chkNoThrow.Checked; int divisor = (chkZero.Checked ? 0 : 1); int Iterations = Convert.ToInt32(txtIterations.Text); int i = 0; ExceptionTest x = new ExceptionTest(); int r = -2; int stat = 0; for(i=0; i < Iterations; i++) { try { r = x.TryDivide(divisor, PreCheck, NoThrow); } catch { stat = -3; } } DateTime stop = DateTime.Now; TimeSpan elapsed = stop - start; txtTime.Text = elapsed.TotalMilliseconds.ToString(); txtReturn.Text = r.ToString(); txtStatus.Text = stat.ToString(); } } class ExceptionTest { public int TryDivide(int quotient, bool precheck, bool nothrow) { if (precheck) { if (quotient == 0) { if (nothrow) { return -9; } else { throw new DivideByZeroException(); } } } else { try { int a; a = 1 / quotient; return a; } catch { if (nothrow) { return -9; } else { throw; } } } return -1; } } 

Así que sí, las excepciones son muy caras.

Y antes de que alguien lo diga, SÍ, probé esto en el modo Release y no solo en el modo Debug . Pruebe el código usted mismo y vea si obtiene resultados significativamente diferentes.

La excepción tiene dos costos: calentamiento de la página en la infraestructura de excepciones (si no está en la memoria y luego en la memoria caché de la CPU) y el costo por tiro para reunir la stack de excepciones, buscar el controlador de excepciones, posiblemente llamar filtros de excepciones, desenrollar la stack, Bloques de finalización de llamada: todas las operaciones para las que el tiempo de ejecución, por diseño, no se optimiza.

Por lo tanto, medir el costo de lanzar excepciones puede ser engañoso. Si escribe un bucle que arroja y atrapa de forma iterativa una excepción, sin mucho trabajo entre el sitio de lanzamiento y el sitio de captura, el costo no se verá tan grande. Sin embargo, eso se debe a que está amortizando el costo de calentamiento de las excepciones, y ese costo es más difícil de medir.

Ciertamente, las excepciones no cuestan nada de lo que parece si la experiencia principal de una persona son las excepciones lanzadas por los progtwigs bajo el depurador. Pero tienen un costo, y es recomendable diseñar bibliotecas en particular, de modo que se puedan evitar las excepciones donde sea necesario para la optimización.

Puede encontrar mucha información útil sobre esto en las respuestas a esta pregunta, incluida una respuesta con 45 votos a favor. ¿Qué tan lentas son las excepciones de .net?

El uso de devoluciones de errores será mucho más costoso que las excepciones, tan pronto como un fragmento de código se olvide de verificar la devolución de errores o no pueda propagarla.

Aún así, asegúrese de no usar excepciones para el flujo de control; úselas solo para indicar que algo salió mal.

En este momento tengo problemas para encontrar documentos que lo respalden, pero tenga en cuenta que cuando lanza una excepción, C # tiene que generar un seguimiento de stack desde el punto donde lo llamó. Las huellas de stack (y la reflexión en general) están lejos de ser libres.

En algunos análisis recientes de rendimiento en el trabajo real, encontramos que un montón de excepciones en las máquinas de gama baja tuvieron un efecto crítico en el rendimiento de la aplicación, tanto que estamos dedicando algunas semanas para ajustar el código para no lanzar tantas.

Cuando digo un efecto crítico, fue en el campo de juego de clavar la CPU de doble núcleo hasta el 95% en el único núcleo de CPU que estaba utilizando la aplicación.

Debería preferir Excepciones sobre códigos de error, y para condiciones de error, pero no use excepciones para el flujo normal del progtwig.

Las excepciones son tareas extremadamente pesadas en comparación con el flujo de trabajo normal. He visto un enorme decrecimiento en el rendimiento de la aplicación utilizando un bloque try-catch.

Lanzar una excepción es una operación relativamente barata. Es su captura lo que incurre en el costo debido a las caminatas de la stack que deben ocurrir para encontrar los controladores de captura, ejecutar el código en los controladores de captura, encontrar los bloques finalmente, ejecutar el código en los bloques finalmente y luego regresar al llamante original.

Se recomienda encarecidamente que no utilice excepciones para el flujo de control. El uso de códigos de retorno para indicar que los errores son caros desde la perspectiva del “tiempo y los materiales”, ya que eventualmente incurrirá en costos de mantenimiento.

Aparte de eso, sus dos ejemplos no coinciden ni son válidos. Como devuelve b , que es de tipo Bar , su primer método probablemente debería ser:

 public Bar Foo(Bar b) { if(b.Success) { return b; } else { throw n.Exception; } } 

que podría ser reescrito como:

 public Bar Foo(Bar b) { if (!b.Success) throw n.Exception; return b; } 

En general, he evitado esto debido a la naturaleza costosa de atrapar la excepción. Puede que no sea tan malo si no es algo que sucede a menudo.

Sin embargo, ¿por qué no volver nulo? Entonces se convierte en esto:

 public Foo Bar(Bar b) { if(b.Success) { return b; } else { return null; } } 

Y luego, cuando llame a Bar (), simplemente verifique que el valor devuelto no sea nulo antes de usar el valor. Esta es una operación mucho menos costosa. Y creo que es una buena práctica porque esta es la técnica que Microsoft ha utilizado en muchas de las funciones integradas de .NET.

Vi muchos sistemas diferentes en los que las excepciones se consideraban una indicación de un problema de software y también medios para proporcionar información sobre los eventos que conducían a él. Eso siempre es pesado en el sistema. Sin embargo, existían sistemas en los que la excepción no era excepcional, ya que el lenguaje en sí utilizaba los mismos métodos o métodos similares para regresar de la rutina, etc. Por lo tanto, se reduce a cómo se integran las excepciones en el lenguaje y el sistema. Hice mis mediciones en java y en un sistema propietario en el que trabajo en este momento y los resultados fueron los esperados; excepcional, las excepciones fueron (20-25)% más caras.

Esto no tiene por qué ser la regla. Me pregunto, por ejemplo, cómo Python se apoya en esto. Ya no hago ningún desarrollo de python, así que no voy a investigar.

Como una posible evidencia anecdótica interesante, tome esto: en un sistema propietario, trabajé hace unos años y el uso poco inteligente de las excepciones para violaciones de protocolo condujo a graves problemas e interrupciones de uno de nuestros sistemas de producción. Se utilizaron excepciones para indicar un campo obligatorio faltante o corrupto en un mensaje recibido desde el exterior. Todo estuvo bien hasta que un día soleado, una compañía menos calificada produjo un nodo que, cuando se puso en funcionamiento, causó muchos problemas. Se tuvieron que eliminar las excepciones, ya que no pudimos hacer frente incluso a un nivel bajo de señalización desde un nodo defectuoso. De hecho, la falla también estaba de nuestro lado: en lugar de seguir las especificaciones del protocolo que describían qué hacer con los mensajes mal formateados (ignorar y pasar el contador de fallas de señalización), intentamos detectar y depurar fallas de software en un nodo externo. Si las excepciones fueran menos costosas, esto no sería tan importante.

De hecho, al probar la seguridad en estos días, siempre tengo un TC o algunos que hacen exactamente eso: generan un gran volumen de situaciones de violación de protocolo. Si los desarrolladores usan excepciones para manejarlas, entonces el sistema puede ponerse de rodillas con bastante facilidad. Cuando esto falla una vez, comenzaré a medir la diferencia otra vez …