c# - prueba el rendimiento de la captura
performance (7)
Este artículo en MSDN establece que puede usar tantos bloques try catch como desee y no incurrir en ningún costo de rendimiento siempre que no se genere una excepción real.
Como siempre creí que un try-catch siempre requiere un pequeño golpe de rendimiento incluso cuando no lancé la excepción, hice una pequeña prueba.
private void TryCatchPerformance()
{
int iterations = 100000000;
Stopwatch stopwatch = Stopwatch.StartNew();
int c = 0;
for (int i = 0; i < iterations; i++)
{
try
{
// c += i * (2 * (int)Math.Floor((double)i));
c += i * 2;
}
catch (Exception ex)
{
throw;
}
}
stopwatch.Stop();
WriteLog(String.Format("With try catch: {0}", stopwatch.ElapsedMilliseconds));
Stopwatch stopwatch2 = Stopwatch.StartNew();
int c2 = 0;
for (int i = 0; i < iterations; i++)
{
// c2 += i * (2 * (int)Math.Floor((double)i));
c2 += i * 2;
}
stopwatch2.Stop();
WriteLog(String.Format("Without try catch: {0}", stopwatch2.ElapsedMilliseconds));
}
La salida que obtengo:
With try catch: 68
Without try catch: 34
¿Entonces parece que el uso de un bloque de prueba no es más rápido después de todo?
Lo que encuentro aún más extraño es que cuando reemplazo el cálculo en el cuerpo de los for-loops por algo más complejo como: c += i * (2 * (int)Math.Floor((double)i));
La diferencia es mucho menos dramática.
With try catch: 640
Without try catch: 655
¿Estoy haciendo algo mal aquí o hay una explicación lógica para esto?
El JIT no realiza la optimización en bloques ''protegidos'' / ''try'' y supongo que dependiendo del código que escriba en los bloques try / catch, esto afectará su rendimiento.
El bloque try / catch / finally / fault en sí no tiene esencialmente sobrecarga en un conjunto de liberación optimizado. Si bien a menudo se agregan IL adicionales para los bloques de captura y finalmente, cuando no se lanza una excepción, hay poca diferencia en el comportamiento. En lugar de un simple ret, usualmente hay un permiso para un ret posterior.
El verdadero costo de los bloques try / catch / finally ocurre cuando se maneja una excepción. En tales casos, se debe crear una excepción, se deben colocar marcas de rastreo de pila y, si se maneja la excepción y se accede a su propiedad StackTrace, se incurre en una caminata de pila. La operación más pesada es la traza de la pila, que sigue las marcas de rastreo de la pila previamente establecidas para crear un objeto StackTrace que se puede usar para mostrar la ubicación del error y las llamadas a las que atravesó.
Si no hay comportamiento en un bloque try / catch, entonces el costo adicional de ''leave to ret'' frente a ''ret'' dominará, y obviamente habrá una diferencia mensurable. Sin embargo, en cualquier otra situación en la que exista algún tipo de comportamiento en la cláusula try, el costo del bloque en sí será totalmente negado.
Tenga en cuenta que solo tengo Mono disponible:
// a.cs
public class x {
static void Main() {
int x = 0;
x += 5;
return ;
}
}
// b.cs
public class x {
static void Main() {
int x = 0;
try {
x += 5;
} catch (System.Exception) {
throw;
}
return ;
}
}
Desmontando estos:
// a.cs
default void Main () cil managed
{
// Method begins at RVA 0x20f4
.entrypoint
// Code size 7 (0x7)
.maxstack 3
.locals init (
int32 V_0)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: ldc.i4.5
IL_0004: add
IL_0005: stloc.0
IL_0006: ret
} // end of method x::Main
y
// b.cs
default void Main () cil managed
{
// Method begins at RVA 0x20f4
.entrypoint
// Code size 20 (0x14)
.maxstack 3
.locals init (
int32 V_0)
IL_0000: ldc.i4.0
IL_0001: stloc.0
.try { // 0
IL_0002: ldloc.0
IL_0003: ldc.i4.5
IL_0004: add
IL_0005: stloc.0
IL_0006: leave IL_0013
} // end .try 0
catch class [mscorlib]System.Exception { // 0
IL_000b: pop
IL_000c: rethrow
IL_000e: leave IL_0013
} // end handler 0
IL_0013: ret
} // end of method x::Main
La principal diferencia que veo es a.cs va directamente a ret
en IL_0006
, mientras que b.cs tiene que leave IL_0013
en IL_006
. Mi mejor conjetura con mi ejemplo, es que la leave
es un salto (relativamente) costoso cuando se compila en código máquina, que puede o no ser el caso, especialmente en su bucle for. Es decir, el try-catch no tiene una carga adicional inherente, pero saltar sobre la captura tiene un costo, como cualquier rama condicional.
Una diferencia de solo 34 milisegundos es menor que el margen de error para una prueba como esta.
Como habrás notado, cuando aumentas la duración de la prueba, esa diferencia simplemente desaparece y el rendimiento de los dos conjuntos de códigos es efectivamente el mismo.
Al hacer este tipo de punto de referencia, intento recorrer cada sección del código durante al menos 20 segundos, preferiblemente más tiempo e idealmente durante varias horas.
el cálculo real es tan mínimo que las mediciones precisas son muy difíciles. Me parece que intentar capturar puede agregar una cantidad fija muy pequeña de tiempo extra a la rutina. Me arriesgaría a adivinar, sin saber nada acerca de cómo se implementan las excepciones en C #, que esto es principalmente la inicialización de las rutas de excepción y quizás solo una pequeña carga en el JIT.
Para cualquier uso real, el tiempo dedicado al cálculo abrumará tanto el tiempo dedicado a jugar con try-catch que el costo de try-catch se puede tomar como casi cero.
El problema es primero en su Código de prueba. usaste cronómetro. Excedido. Milisegundos que muestra solo la parte de milisegundos del tiempo transcurrido, usa TotalMilliseconds para obtener la parte completa ...
Si no se lanza una excepción, la diferencia es mínima
Pero la verdadera pregunta es: "¿Debo controlar las excepciones o dejar que C # maneje el lanzamiento de la Excepción?"
Claramente ... manejar solo ... Intente ejecutar esto:
private void TryCatchPerformance()
{
int iterations = 10000;
textBox1.Text = "";
Stopwatch stopwatch = Stopwatch.StartNew();
int c = 0;
for (int i = 0; i < iterations; i++)
{
try
{
c += i / (i % 50);
}
catch (Exception)
{
}
}
stopwatch.Stop();
Debug.WriteLine(String.Format("With try catch: {0}", stopwatch.Elapsed.TotalSeconds));
Stopwatch stopwatch2 = Stopwatch.StartNew();
int c2 = 0;
for (int i = 0; i < iterations; i++)
{
int iMod50 = (i%50);
if(iMod50 > 0)
c2 += i / iMod50;
}
stopwatch2.Stop();
Debug.WriteLine( String.Format("Without try catch: {0}", stopwatch2.Elapsed.TotalSeconds));
}
Salida: OBSOLETO: ¡mira abajo! Con try catch: 1.9938401
Sin intentar atrapar: 8.92E-05
Increíble, solo 10000 objetos, con 200 excepciones.
CORRECCIÓN: Ejecuto mi código en la ventana DEBUG y VS Written Exception to Output. Estos son los Resultados de la RELEASE Mucho menos sobrecarga, pero aún mejora en 7.500%.
Con try catch: 0.0546915
Comprobando solo: 0.0007294
Con try catch Lanzando mi propio objeto Exception: 0.0265229
Consulte la 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 elevada, y algunas tienen una sobrecarga cero, cuando no se producen excepciones.