¿Los bloques try/catch dañan el rendimiento cuando no se lanzan excepciones?


Durante una revisión de código con un empleado de Microsoft nos encontramos con una gran sección de código dentro de un bloque try{}. Ella y un representante de TI sugirieron que esto puede tener efectos en el rendimiento del código. De hecho, sugirieron que la mayor parte del código debería estar fuera de los bloques try/catch, y que solo deberían revisarse las secciones importantes. El empleado de Microsoft agregó y dijo que un próximo libro blanco advierte contra los bloqueos incorrectos try/catch.

He mirado alrededor y lo encontré puede afecta a las optimizaciones , pero parece que solo se aplica cuando una variable se comparte entre ámbitos.

No estoy preguntando acerca de la mantenibilidad del código, o incluso el manejo de las excepciones correctas (el código en cuestión necesita re-factorización, sin duda). Tampoco me estoy refiriendo al uso de excepciones para el control de flujo, esto es claramente incorrecto en la mayoría de los casos. Esos son temas importantes (algunos son más importantes), pero no el enfoque aquí.

¿Cómo afectan los bloques try / catch al rendimiento cuando las excepciones son no lanzado?

EDITAR: Estoy añadiendo una recompensa. Hay respuestas interesantes, pero me gustaría obtener más información.

Author: John Saunders, 2009-08-20

10 answers

Comprobarlo.

static public void Main(string[] args)
{
    Stopwatch w = new Stopwatch();
    double d = 0;

    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        try
        {
            d = Math.Sin(1);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }

    w.Stop();
    Console.WriteLine(w.Elapsed);
    w.Reset();
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        d = Math.Sin(1);
    }

    w.Stop();
    Console.WriteLine(w.Elapsed);
}

Salida:

00:00:00.4269033  // with try/catch
00:00:00.4260383  // without.

En milisegundos:

449
416

Nuevo código:

for (int j = 0; j < 10; j++)
{
    Stopwatch w = new Stopwatch();
    double d = 0;
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        try
        {
            d = Math.Sin(d);
        }

        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }

        finally
        {
            d = Math.Sin(d);
        }
    }

    w.Stop();
    Console.Write("   try/catch/finally: ");
    Console.WriteLine(w.ElapsedMilliseconds);
    w.Reset();
    d = 0;
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        d = Math.Sin(d);
        d = Math.Sin(d);
    }

    w.Stop();
    Console.Write("No try/catch/finally: ");
    Console.WriteLine(w.ElapsedMilliseconds);
    Console.WriteLine();
}

Nuevos resultados:

   try/catch/finally: 382
No try/catch/finally: 332

   try/catch/finally: 375
No try/catch/finally: 332

   try/catch/finally: 376
No try/catch/finally: 333

   try/catch/finally: 375
No try/catch/finally: 330

   try/catch/finally: 373
No try/catch/finally: 329

   try/catch/finally: 373
No try/catch/finally: 330

   try/catch/finally: 373
No try/catch/finally: 352

   try/catch/finally: 374
No try/catch/finally: 331

   try/catch/finally: 380
No try/catch/finally: 329

   try/catch/finally: 374
No try/catch/finally: 334
 163
Author: Ben M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-02 11:44:28

Después de ver todas las estadísticas para con try/catch y sin try/catch, la curiosidad me obligó a mirar detrás de para ver qué se genera para ambos casos. Aquí está el código:

C#:

private static void TestWithoutTryCatch(){
    Console.WriteLine("SIN(1) = {0} - No Try/Catch", Math.Sin(1)); 
}

MSIL:

.method private hidebysig static void  TestWithoutTryCatch() cil managed
{
  // Code size       32 (0x20)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "SIN(1) = {0} - No Try/Catch"
  IL_0006:  ldc.r8     1.
  IL_000f:  call       float64 [mscorlib]System.Math::Sin(float64)
  IL_0014:  box        [mscorlib]System.Double
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_001e:  nop
  IL_001f:  ret
} // end of method Program::TestWithoutTryCatch

C#:

private static void TestWithTryCatch(){
    try{
        Console.WriteLine("SIN(1) = {0}", Math.Sin(1)); 
    }
    catch (Exception ex){
        Console.WriteLine(ex);
    }
}

MSIL:

.method private hidebysig static void  TestWithTryCatch() cil managed
{
  // Code size       49 (0x31)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Exception ex)
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  ldstr      "SIN(1) = {0}"
    IL_0007:  ldc.r8     1.
    IL_0010:  call       float64 [mscorlib]System.Math::Sin(float64)
    IL_0015:  box        [mscorlib]System.Double
    IL_001a:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object)
    IL_001f:  nop
    IL_0020:  nop
    IL_0021:  leave.s    IL_002f //JUMP IF NO EXCEPTION
  }  // end .try
  catch [mscorlib]System.Exception 
  {
    IL_0023:  stloc.0
    IL_0024:  nop
    IL_0025:  ldloc.0
    IL_0026:  call       void [mscorlib]System.Console::WriteLine(object)
    IL_002b:  nop
    IL_002c:  nop
    IL_002d:  leave.s    IL_002f
  }  // end handler
  IL_002f:  nop
  IL_0030:  ret
} // end of method Program::TestWithTryCatch

No soy un experto en IL, pero podemos ver que un objeto de excepción local se crea en la cuarta línea .locals init ([0] class [mscorlib]System.Exception ex) después de que las cosas son bastante iguales que para el método sin try/catch hasta la línea diecisiete IL_0021: leave.s IL_002f. Si un la excepción ocurre el control salta a la línea IL_0025: ldloc.0 de lo contrario saltamos a label IL_002d: leave.s IL_002f y la función regresa.

Puedo asumir con seguridad que si no se producen excepciones, entonces es la sobrecarga de crear variables locales para contener objetos de excepción solo y una instrucción de salto.

 80
Author: TheVillageIdiot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-01 10:30:11

No. Si las optimizaciones triviales que un bloque try/finally impide realmente tienen un impacto medible en su programa, probablemente no debería usar.NET en primer lugar.

 51
Author: John Kugelman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-20 20:00:25

Explicación bastante completa del modelo de excepción. NET.

El rendimiento de Rico Mariani Tidbits: Costo de excepción: Cuándo lanzar y cuándo no

El primer tipo de coste es el estático costo de tener el manejo de excepciones en tu código en absoluto. Excepciones administradas en realidad hacer comparativamente bien aquí, con lo que quiero decir que el costo estático puede ser mucho más bajo que digamos en C++. Por qué es ¿esto? Bueno, el costo estático es realmente incurridos en dos tipos de lugar: Primero, los sitios reales de try / finally / catch / throw where there's código para esas construcciones. Segundo, en código sin cambiar, ahí está el sigilo costo asociado con el seguimiento de todos los objetos que deben ser destructed in the event that an se lanza la excepción. Hay un cantidad considerable de lógica de limpieza que debe estar presente y la escurridiza es que incluso el código que no sí mismo lanzar o atrapar o de otra manera tener cualquier uso abierto de excepciones todavía lleva el carga de saber cómo limpiar después de sí mismo.

Dmitriy Zaslavskiy:

Según la nota de Chris Brumme: Hay también un costo relacionado con el hecho de que el algunos optimización no están siendo realizado por JIT en presencia de captura

 30
Author: arul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-20 20:12:41

La estructura es diferente en el ejemplo de Ben M. Se extenderá arriba dentro del bucle interno for que hará que no sea una buena comparación entre los dos casos.

Lo siguiente es más preciso para la comparación donde todo el código a verificar (incluida la declaración de variables) está dentro del bloque Try/Catch:

        for (int j = 0; j < 10; j++)
        {
            Stopwatch w = new Stopwatch();
            w.Start();
            try { 
                double d1 = 0; 
                for (int i = 0; i < 10000000; i++) { 
                    d1 = Math.Sin(d1);
                    d1 = Math.Sin(d1); 
                } 
            }
            catch (Exception ex) {
                Console.WriteLine(ex.ToString()); 
            }
            finally { 
                //d1 = Math.Sin(d1); 
            }
            w.Stop(); 
            Console.Write("   try/catch/finally: "); 
            Console.WriteLine(w.ElapsedMilliseconds); 
            w.Reset(); 
            w.Start(); 
            double d2 = 0; 
            for (int i = 0; i < 10000000; i++) { 
                d2 = Math.Sin(d2);
                d2 = Math.Sin(d2); 
            } 
            w.Stop(); 
            Console.Write("No try/catch/finally: "); 
            Console.WriteLine(w.ElapsedMilliseconds); 
            Console.WriteLine();
        }

Cuando ejecuté el código de prueba original de Ben M , noté una diferencia tanto en Debug como en Releas configuración.

Esta versión, noté una diferencia en la versión de depuración (en realidad más que la otra versión), pero no fue ninguna diferencia en la versión de lanzamiento.

Conclusión:
Basado en estas pruebas, creo que podemos decir que Try / Catch tiene un pequeño impacto en el rendimiento.

EDITAR:
Traté de aumentar el valor del bucle de 10000000 a 1000000000, y corrí de nuevo en la versión para obtener algunas diferencias en la versión, y el resultado fue este:

   try/catch/finally: 509
No try/catch/finally: 486

   try/catch/finally: 479
No try/catch/finally: 511

   try/catch/finally: 475
No try/catch/finally: 477

   try/catch/finally: 477
No try/catch/finally: 475

   try/catch/finally: 475
No try/catch/finally: 476

   try/catch/finally: 477
No try/catch/finally: 474

   try/catch/finally: 475
No try/catch/finally: 475

   try/catch/finally: 476
No try/catch/finally: 476

   try/catch/finally: 475
No try/catch/finally: 476

   try/catch/finally: 475
No try/catch/finally: 474

Ves que el resultado es inconsecuente. En algunos casos, la versión que usa Try / Catch es realmente más rápida.

 20
Author: awe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-25 09:40:11

Probé el impacto real de un try..catch en un bucle apretado, y es demasiado pequeño por sí mismo para ser una preocupación de rendimiento en cualquier situación normal.

Si el bucle hace muy poco trabajo (en mi prueba hice un x++), puede medir el impacto del manejo de excepciones. El bucle con manejo de excepciones tardó unas diez veces más en ejecutarse.

Si el bucle hace algún trabajo real (en mi prueba llamé al Int32.Método de análisis), el manejo de excepciones tiene muy poco impacto para ser medible. Me tiene una diferencia mucho mayor al intercambiar el orden de los bucles...

 12
Author: Guffa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-20 20:32:31

Los bloques Try catch tienen un impacto insignificante en el rendimiento, pero el lanzamiento de excepciones puede ser bastante considerable, probablemente aquí es donde su compañero de trabajo se confundió.

 8
Author: RHicke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-20 20:29:07

El try/catch TIENE un impacto en el rendimiento.

Pero no es un gran impacto. la complejidad try/catch es generalmente O (1), al igual que una asignación simple, excepto cuando se colocan en un bucle. Así que tienes que usarlos sabiamente.

Aquí es una referencia sobre el rendimiento try/catch (sin embargo, no explica la complejidad del mismo, pero está implícito). Echa un vistazo a Lanzar menos excepciones sección

 5
Author: Isaac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-19 17:18:01

En teoría, un bloque try / catch no tendrá ningún efecto en el comportamiento del código a menos que ocurra una excepción. Hay algunas circunstancias raras, sin embargo, donde la existencia de un bloque try/catch puede tener un efecto importante, y algunos poco comunes, pero difícilmente oscuros, donde el efecto puede ser notable. La razón de esto es que dado código como:

Action q;
double thing1()
  { double total; for (int i=0; i<1000000; i++) total+=1.0/i; return total;}
double thing2()
  { q=null; return 1.0;}
...
x=thing1();     // statement1
x=thing2(x);    // statement2
doSomething(x); // statement3

El compilador puede ser capaz de optimizar statement1 basado en el hecho de que statement2 está garantizado para ejecutarse antes de statement3. Si el compilador puede reconocer que thing1 no tiene efectos secundarios y thing2 en realidad no usa x, puede omitir thing1 por completo. Si [como en este caso] thing1 era costoso, eso podría ser una optimización importante, aunque los casos en los que thing1 es costoso también son aquellos en los que el compilador tendría menos probabilidades de optimizar. Supongamos que el código fue cambiado:

x=thing1();      // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x);  // statement3

Ahora existe una secuencia de eventos donde statement3 podría ejecutarse sin que statement2 se haya ejecutado. Incluso si nada en el código para thing2 podría lanzar una excepción, sería posible que otro subproceso pudiera usar un Interlocked.CompareExchange para notar que q se borró y se estableció en Thread.ResetAbort, y luego realizar un Thread.Abort() antes de que statement2 escribiera su valor a x. Entonces el catch ejecutaría Thread.ResetAbort() [vía delegado q], permitiendo que la ejecución continuara con statement3. Por supuesto, tal secuencia de eventos sería excepcionalmente improbable, pero se requiere un compilador para generar código que funcione de acuerdo con las especificaciones, incluso cuando tales eventos improbables ocurren.

En general, es mucho más probable que el compilador note oportunidades para omitir bits simples de código que complejos, y por lo tanto sería raro que un try/catch pudiera afectar mucho el rendimiento si nunca se lanzan excepciones. Sin embargo, hay algunas situaciones en las que la existencia de un bloque try/catch puede evitar optimizaciones que, de no ser por el try/catch, habrían permitido que el código se ejecutara más rápido.

 4
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-28 16:11:52

Vea discusión sobre la implementación try/catch para una discusión sobre cómo funcionan los bloques try/catch, y cómo algunas implementaciones tienen una sobrecarga alta, y algunas tienen una sobrecarga cero, cuando no se producen excepciones. En particular, creo que la implementación de Windows de 32 bits tiene una alta sobrecarga, y la implementación de 64 bits no.

 3
Author: Ira Baxter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:26