Pregunta

Hemos visto un montón de preguntas acerca de cuándo y por qué utilizar try / catch y try / catch / finally. Y sé que hay definitivamente un caso de uso para try / finally (especialmente ya que es la forma en que se implementa la declaración using).

También hemos visto preguntas sobre la sobrecarga de try / catch y excepciones .

La pregunta he vinculado, sin embargo, no habla de la sobrecarga de tener sólo tratar -por fin-.

Si se asume que no hay excepciones a todo lo que sucede en el bloque try, ¿cuál es la sobrecarga de asegurarse de que los estados finally se ejecutan a la salida del bloque de try (a veces mediante la devolución de la función)?

Una vez más, estoy pidiendo sólo alrededor try / finally, sin catch, sin lanzamiento de excepciones.

Gracias!

EDIT:. De acuerdo, voy a tratar de mostrar mi caso uso un poco mejor

¿Qué debo usar, DoWithTryFinally o DoWithoutTryFinally?

public bool DoWithTryFinally()
{
  this.IsBusy = true;

  try
  {
    if (DoLongCheckThatWillNotThrowException())
    {
      this.DebugLogSuccess();
      return true;
    }
    else
    {
      this.ErrorLogFailure();
      return false;
    }
  }
  finally
  {
    this.IsBusy = false;
  }
}

public bool DoWithoutTryFinally()
{
  this.IsBusy = true;

  if (DoLongCheckThatWillNotThrowException())
  {
    this.DebugLogSuccess();

    this.IsBusy = false;
    return true;
  }
  else
  {
    this.ErrorLogFailure();

    this.IsBusy = false;
    return false;
  }
}

Este caso es demasiado simplista, porque sólo hay dos puntos de retorno, pero se imaginan si había cuatro o diez ... ... o cien.

En algún momento me gustaría utilizar try / finally por las siguientes razones:

  • Mantenga a principios seco (sobre todo porque el número de puntos de salida se hace mayor)
  • Si resulta que estoy equivocado acerca de mi función interna no lanzar una excepción, entonces yo quiero para asegurarse de this.Working se establece en false.

Así que, hipotéticamente, dada problemas de rendimiento, facilidad de mantenimiento, y los principios en seco, por lo que el número de puntos de salida (especialmente si puede asumen que todas las excepciones internas son capturados) hago Quiero que incurrir en cualquier pérdida de rendimiento está asociado con try / finally?

editar # 2: He cambiado el nombre de this.Working a this.IsBusy. Lo sentimos, se olvidó de mencionar esto es multiproceso (aunque sólo un hilo será siempre realmente llamar al método); otros temas serán de votación para ver si el objeto está haciendo su trabajo. El valor de retorno es más que el éxito o fracaso de si el trabajo fue como se esperaba.

¿Fue útil?

Solución

¿Por qué no mira a lo que ofrece?

Aquí es un simple trozo de código en C #:

    static void Main(string[] args)
    {
        int i = 0;
        try
        {
            i = 1;
            Console.WriteLine(i);
            return;
        }
        finally
        {
            Console.WriteLine("finally.");
        }
    }

Y aquí está la IL dando como resultado la generación de depuración:

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init ([0] int32 i)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: nop 
    L_0004: ldc.i4.1 
    L_0005: stloc.0 
    L_0006: ldloc.0 // here's the WriteLine of i 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32)
    L_000c: nop 
    L_000d: leave.s L_001d // this is the flavor of branch that triggers finally
    L_000f: nop 
    L_0010: ldstr "finally."
    L_0015: call void [mscorlib]System.Console::WriteLine(string)
    L_001a: nop 
    L_001b: nop 
    L_001c: endfinally 
    L_001d: nop 
    L_001e: ret 
    .try L_0003 to L_000f finally handler L_000f to L_001d
}

Y aquí está el ensamblador generado por el compilador JIT de depuración cuando se ejecuta en:

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        edi 
00000004  push        esi 
00000005  push        ebx 
00000006  sub         esp,34h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[00288D34h],0 
00000028  je          0000002F 
0000002a  call        59439E21 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  nop 
        int i = 0;
00000035  xor         edx,edx 
00000037  mov         dword ptr [ebp-40h],edx 
        try
        {
0000003a  nop 
            i = 1;
0000003b  mov         dword ptr [ebp-40h],1 
            Console.WriteLine(i);
00000042  mov         ecx,dword ptr [ebp-40h] 
00000045  call        58DB2EA0 
0000004a  nop 
            return;
0000004b  nop 
0000004c  mov         dword ptr [ebp-20h],0 
00000053  mov         dword ptr [ebp-1Ch],0FCh 
0000005a  push        4E1584h 
0000005f  jmp         00000061 
        }
        finally
        {
00000061  nop 
            Console.WriteLine("finally.");
00000062  mov         ecx,dword ptr ds:[036E2088h] 
00000068  call        58DB2DB4 
0000006d  nop 
        }
0000006e  nop 
0000006f  pop         eax 
00000070  jmp         eax 
00000072  nop 
    }
00000073  nop 
00000074  lea         esp,[ebp-0Ch] 
00000077  pop         ebx 
00000078  pop         esi 
00000079  pop         edi 
0000007a  pop         ebp 
0000007b  ret 
0000007c  mov         dword ptr [ebp-1Ch],0 
00000083  jmp         00000072 

Ahora, si me comente la oportunidad y, finalmente, y el regreso, me sale el montaje casi idénticos desde el JIT. Las diferencias son que verá un salto en el bloque finally y algo de código para averiguar dónde ir después de la que se ejecuta finalmente. Así que estamos hablando de diferencias pequeñas. En el comunicado, el salto en los finalmente tendrá optimizados a cabo - los apoyos son instrucciones nop, por lo que esto se convertiría en un salto a la siguiente instrucción, que es también un nop - que es una optimización mirilla fácil. El POP EAX y EAX continuación JMP es igualmente barato.

    {
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        edi 
00000004  push        esi 
00000005  push        ebx 
00000006  sub         esp,34h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[00198D34h],0 
00000028  je          0000002F 
0000002a  call        59549E21 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  nop 
        int i = 0;
00000035  xor         edx,edx 
00000037  mov         dword ptr [ebp-40h],edx 
        //try
        //{
            i = 1;
0000003a  mov         dword ptr [ebp-40h],1 
            Console.WriteLine(i);
00000041  mov         ecx,dword ptr [ebp-40h] 
00000044  call        58EC2EA0 
00000049  nop 
        //    return;
        //}
        //finally
        //{
            Console.WriteLine("finally.");
0000004a  mov         ecx,dword ptr ds:[034C2088h] 
00000050  call        58EC2DB4 
00000055  nop 
        //}
    }
00000056  nop 
00000057  lea         esp,[ebp-0Ch] 
0000005a  pop         ebx 
0000005b  pop         esi 
0000005c  pop         edi 
0000005d  pop         ebp 
0000005e  ret 

Así que estamos hablando costes muy, muy pequeñas para try / finally. Hay muy pocos dominios problema en el que esto es importante. Si estás haciendo algo así como establecimiento de memoria y poner un try / finally alrededor de cada byte se copia y luego proceder a copiar cientos de MB de datos, pude ver que ser un problema, pero en la mayoría de uso? Insignificante.

Otros consejos

Así que vamos a asumir que hay una sobrecarga. ¿Vas a dejar de usar finally entonces? Ojalá no.

OMI métricas de rendimiento sólo son relevantes si se puede elegir entre diferentes opciones. No puedo ver cómo se puede obtener la semántica de finally sin utilizar finally.

try/finally es muy ligero. En realidad, por lo que es try/catch/finally siempre y cuando no excepción.

Yo tenía un perfil rápido de aplicaciones que hice hace un tiempo para probarlo; en un bucle estrecho, que se sumó nada en absoluto que el tiempo de ejecución.

que había puesto de nuevo, pero era muy simple; basta con ejecutar un bucle estrecho hacer algo, con un try/catch/finally que no lanza ninguna excepción dentro del bucle, y el tiempo los resultados en contra de una versión sin la try/catch/finally.

Vamos a realmente poner algunos números de referencia a esto. Lo que este punto de referencia muestra es que, efectivamente, el tiempo de tener un try / finally es casi tan pequeño como la sobrecarga de una llamada a una función vacía (probablemente mejor poner: "un salto a la siguiente instrucción" como el experto IL declaró que más arriba).

            static void RunTryFinallyTest()
            {
                int cnt = 10000000;

                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));

                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));

                Console.ReadKey();
            }

            static double TryFinallyBenchmarker(int count, bool useTryFinally)
            {
                int over1 = count + 1;
                int over2 = count + 2;

                if (!useTryFinally)
                {
                    var sw = Stopwatch.StartNew();
                    for (int i = 0; i < count; i++)
                    {
                        // do something so optimization doesn't ignore whole loop. 
                        if (i == over1) throw new Exception();
                        if (i == over2) throw new Exception();
                    }
                    return sw.Elapsed.TotalMilliseconds;
                }
                else
                {
                    var sw = Stopwatch.StartNew();
                    for (int i = 0; i < count; i++)
                    {
                        // do same things, just second in the finally, make sure finally is 
                        // actually doing something and not optimized out
                        try
                        {
                            if (i == over1) throw new Exception();
                        } finally
                        {
                            if (i == over2) throw new Exception();
                        }
                    }
                    return sw.Elapsed.TotalMilliseconds;
                }
            }

Resultado: 33,33,32,35,32 63,64,69,66,66 (Milisegundos, asegúrese de que tiene optimización de código de)

Así que alrededor del 33 milisegundos generales para el try / finally en 10 millones bucles.

Por try / finally entonces, estamos hablando 0,033 / 10000000 =

3.3 nanosegundos o 3.3 mil millonésima parte de un segundo por encima de un try / finally.

Lo que dijo Andrew Barber. Las declaraciones reales try / catch no agregan / sobrecarga insignificante a menos que se produce una excepción. No hay nada realmente especial acerca finalmente. Su código sólo salta siempre a fin después de que el código en las declaraciones try + captura se realiza

En niveles más bajos finally es tan caro como un else si no cumple la condición. En realidad, es un salto en ensamblador (IL).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top