¿Qué tan caras son las excepciones en C#?
¿Qué tan caras son las excepciones en C#? Parece que no son increíblemente caros siempre que la pila no sea profunda; sin embargo, he leído informes contradictorios.
¿Existe algún informe definitivo que no haya sido refutado?
Habiendo leído que las excepciones son costosas en términos de rendimiento, preparé un programa de medición simple, muy similar al que Jon Skeet publicó hace años . Menciono esto aquí principalmente para proporcionar números actualizados.
El programa tardó menos de 29914 milisegundos en procesar un millón de excepciones, lo que equivale a 33 excepciones por milisegundo . Esto es lo suficientemente rápido como para hacer de las excepciones una alternativa viable a los códigos de retorno en la mayoría de las situaciones.
Sin embargo, tenga en cuenta que con códigos de retorno en lugar de excepciones, el mismo programa se ejecuta en 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 destacó Rico Mariani, estos números también son mínimos. En la práctica, lanzar y capturar una excepción llevará más tiempo.
Medido en una computadora portátil con Intel Core2 Duo T8100 @ 2,1 GHz con .NET 4.0 en la versión de lanzamiento que no se ejecuta en 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;
}
Supongo que estoy de acuerdo en que si el rendimiento de las excepciones afecta tu aplicación, entonces estás lanzando MUCHAS de ellas. Las excepciones deben ser para condiciones excepcionales, no como manejo de errores de rutina.
Dicho esto, lo que recuerdo de cómo se manejan las excepciones es esencialmente recorrer la pila para encontrar una declaración de captura 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.
En mi caso, las excepciones eran muy caras. 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 unos 30 segundos. Esta función se llama al menos 32.000 veces al inicio. El código no es tan claro en cuanto a cuál es la intención, pero el ahorro de costos fue enorme.
Hice mis propias mediciones para descubrir qué tan graves eran las implicaciones de las excepciones. No intenté medir el tiempo absoluto para lanzar/atrapar una excepción. Lo que más me interesaba era saber cuánto más lento se volverá un bucle si se lanza una excepción en cada pasada. 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.