¿Qué tan costosas son las excepciones en C #?

¿Qué tan costosas son las excepciones en C #? Parece que no son increíblemente caros, siempre y cuando la stack no sea profunda; sin embargo, he leído informes contradictorios.

¿Hay algún informe definitivo que no haya sido refutado?

Jon Skeet escribió Exceptions and Performance en .NET en enero de 2006

Que fue actualizado Exceptions and Performance Redux (gracias @Gulzar)

A lo que Rico Mariani intervino en The True Cost of .NET Exceptions – Solution


También referencia: Krzysztof Cwalina – Actualización de las Pautas de Diseño: Lanzamiento de Excepción

Supongo que estoy en el campo de que si el rendimiento de las excepciones afecta tu aplicación, estás lanzando MUCHOS de ellos. Las excepciones deben ser para condiciones excepcionales, no como manejo de errores de rutina.

Dicho esto, mi recuerdo de cómo se manejan las excepciones es, esencialmente, subir la stack encontrando una statement catch que coincida con el tipo de excepción lanzada. Por lo tanto, el rendimiento se verá más afectado por la profundidad de la captura y la cantidad de declaraciones de captura que tenga.

Después de leer que las excepciones son costosas en términos de rendimiento, armé un progtwig de medición simple, muy similar al que publicó Jon Skeet hace años . Menciono esto aquí principalmente para proporcionar números actualizados.

Tomó el progtwig por debajo de 29914 milisegundos para procesar un millón de excepciones, lo que equivale a 33 excepciones por milisegundo . Eso es lo suficientemente rápido como para hacer que las excepciones sean una alternativa viable a los códigos de retorno para la mayoría de las situaciones.

Sin embargo, tenga en cuenta que con los códigos de retorno en lugar de excepciones, el mismo progtwig ejecuta menos de un milisegundo, lo que significa que las excepciones son al menos 30,000 veces más lentas que los códigos de retorno . Como subrayó Rico Mariani, estos números también son números mínimos. En la práctica, lanzar y atrapar una excepción tomará más tiempo.

Medido en una computadora portátil con Intel Core 2 Duo T8100 a 2,1 GHz con .NET 4.0 en la versión de comstackción no se ejecuta bajo el depurador (lo que lo haría mucho más lento).

Este es mi código de prueba:

static void Main(string[] args) { int iterations = 1000000; Console.WriteLine("Starting " + iterations.ToString() + " iterations...\n"); var stopwatch = new Stopwatch(); // Test exceptions stopwatch.Reset(); stopwatch.Start(); for (int i = 1; i <= iterations; i++) { try { TestExceptions(); } catch (Exception) { // Do nothing } } stopwatch.Stop(); Console.WriteLine("Exceptions: " + stopwatch.ElapsedMilliseconds.ToString() + " ms"); // Test return codes stopwatch.Reset(); stopwatch.Start(); int retcode; for (int i = 1; i <= iterations; i++) { retcode = TestReturnCodes(); if (retcode == 1) { // Do nothing } } stopwatch.Stop(); Console.WriteLine("Return codes: " + stopwatch.ElapsedMilliseconds.ToString() + " ms"); Console.WriteLine("\nFinished."); Console.ReadKey(); } static void TestExceptions() { throw new Exception("Failed"); } static int TestReturnCodes() { return 1; } 

Los objetos de excepción de Barebones en C # son bastante livianos; por lo general, la capacidad de encapsular una InnerException hace pesado cuando el árbol de objetos se vuelve demasiado profundo.

En cuanto a un informe definitivo, no conozco ninguno, aunque un perfil de dotTrace superficial (o cualquier otro generador de perfiles) para el consumo de memoria y la velocidad será bastante fácil de hacer.

En mi caso, las excepciones fueron muy costosas. Reescribí esto:

 public BlockTemplate this[int x,int y, int z] { get { try { return Data.BlockTemplate[World[Center.X + x, Center.Y + y, Center.Z + z]]; } catch(IndexOutOfRangeException e) { return Data.BlockTemplate[BlockType.Air]; } } } 

Dentro de esto:

 public BlockTemplate this[int x,int y, int z] { get { int ix = Center.X + x; int iy = Center.Y + y; int iz = Center.Z + z; if (ix < 0 || ix >= World.GetLength(0) || iy < 0 || iy >= World.GetLength(1) || iz < 0 || iz >= World.GetLength(2)) return Data.BlockTemplate[BlockType.Air]; return Data.BlockTemplate[World[ix, iy, iz]]; } } 

Y notó un buen aumento de velocidad de aproximadamente 30 segundos. Esta función se llama al menos 32K veces al inicio. El código no es tan claro como cuál es la intención, pero el ahorro de costos fue enorme.

El rendimiento alcanzado con excepciones parece estar en el punto de generar el objeto de excepción (aunque demasiado pequeño para causar preocupaciones el 90% del tiempo). Por lo tanto, la recomendación es perfilar su código: si las excepciones están causando un impacto en el rendimiento, escriba un nuevo método de alto rendimiento que no use excepciones. (Un ejemplo que viene a la mente sería (TryParse introdujo para superar problemas de rendimiento con Parse que usa excepciones)

Sin embargo, en la mayoría de los casos, las excepciones no causan resultados de rendimiento significativos en la mayoría de las situaciones, por lo que la Guía de diseño de MS debe informar las fallas arrojando excepciones.

Hice mis propias mediciones para descubrir cuán grave es la implicación de las excepciones. No intenté medir el tiempo absoluto para arrojar / atrapar excepción. Lo que más me interesó fue saber cuánto más lento se volverá un bucle si se lanza una excepción en cada pase. El código de medición se ve así

  for(; ; ) { iValue = Level1(iValue); lCounter += 1; if(DateTime.Now >= sFinish) break; } 

vs

  for(; ; ) { try { iValue = Level3Throw(iValue); } catch(InvalidOperationException) { iValue += 3; } lCounter += 1; if(DateTime.Now >= sFinish) break; } 

La diferencia es 20 veces. El segundo fragmento es 20 veces más lento.