尝试/终于在C#中的开销?

Pla*_*ure 71 .net c# performance try-finally

我们已经看到很多关于何时以及为何使用try/ catchtry/ catch/的问题finally.我知道try/ 肯定有一个用例finally(特别是因为它是using语句实现的方式).

我们还看到了有关try/catch和异常开销的问题.

然而,我所链接的问题并没有谈到JUST try-finally的开销.

假设try块中发生的任何事情都没有异常,那么确保finally语句在离开try块时执行的开销是多少(有时是从函数返回)?

再一次,我只询问try/ finally,不catch,不抛出异常.

谢谢!

编辑:好的,我将尝试更好地展示我的用例.

我应该使用哪个,DoWithTryFinally或者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;
  }
}
Run Code Online (Sandbox Code Playgroud)

这种情况过于简单,因为只有两个返回点,但想象一下是否有四个......或十个......或一百个.

在某些时候,我想使用try/ finally出于以下原因:

  • 坚持DRY原则(特别是当出口点数增加时)
  • 如果事实证明我的内部函数没有抛出异常,那么我想确保this.Working设置为false.

所以假设,考虑到性能问题,可维护性和DRY原则,对于多少个退出点(特别是如果我可以假设所有内部异常都被捕获),我是否希望产生与try/ 相关的任何性能损失finally

编辑#2:我改名this.Workingthis.IsBusy.对不起,忘记提到这是多线程的(虽然只有一个线程实际上会调用该方法); 其他线程将轮询以查看对象是否正在执行其工作.如果工作按预期进行,则返回值仅仅是成功或失败.

pli*_*nth 95

为什么不看看你到底得到了什么?

这是C#中的一小段代码:

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

以下是调试版本中生成的IL:

.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
}
Run Code Online (Sandbox Code Playgroud)

这是在调试中运行时由JIT生成的程序集:

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 
Run Code Online (Sandbox Code Playgroud)

现在,如果我注释掉try和finally以及return,我会从JIT获得几乎相同的程序集.您将看到的差异是跳转到finally块和一些代码以确定在执行finally之后要去哪里.所以你在谈论TINY的差异.在发布中,跳转到finally将被优化掉 - 大括号是nop指令,所以这将成为跳转到下一条指令,这也是一个nop - 这是一个简单的窥视孔优化.pop eax和jmp eax同样便宜.

    {
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 
Run Code Online (Sandbox Code Playgroud)

因此,您正在谈论尝试/最终的非常非常小的成本.很重要的问题领域很少.如果你正在做类似memcpy的事情,并在每个被复制的字节周围尝试/最后,然后继续复制数百MB的数据,我可以看到这是一个问题,但在大多数情况下?可以忽略不计.


Bri*_*sen 53

所以我们假设有一个开销.你打算停止使用finally吗?希望不是.

IMO绩效指标仅在您可以选择不同选项时才相关.我无法看到你如何在finally不使用的情况下获得语义finally.

  • 一个很好的补充点; 如果你*需要*`finally`,你需要它! (8认同)
  • 更新后的问题中的两种方法并没有真正比较,因为它们在出现错误时表现不同.我知道该方法不应该抛出异常,但您可能仍会遇到异常,例如OutOfMemoryException,ThreadAbortException和其他一些异常.在这种情况下,方法的行为会有所不同. (4认同)
  • @BrianRasmussen你有问题的性能问题吗?"我不知道如何在不使用最终的情况下获得最终的语义" - 谁问了这个问题?真正的问题是:"在C#中尝试/最终的开销?" 相当无辜,除非我们必须承担与绩效相关的问题,即使是像这样的简单但低级别的问题,也必须不赞成. (3认同)
  • 编辑了我的问题.当我不是真的担心例外情况时,我特别要求使用它...我可以*在不同的选项之间进行选择(根据你的答案).请根据您的想法更新. (2认同)
  • 有时您可以在循环中将它们移得更远。例如,Delphi 中的 try/catch 或 try/finally 相当昂贵,所以我不得不这样做几次。 (2认同)

And*_*ber 28

try/finally非常轻巧.实际上,try/catch/finally只要没有抛出异常,也是如此.

我有一个快速的配置文件应用程序,我前一段时间测试它; 在一个紧凑的循环中,它确实没有添加任何执行时间.

我会再发一次,但这很简单; 只是运行一个紧凑的循环做一些事情,一个try/catch/finally不会在循环中抛出任何异常,并将结果与​​没有try/catch/finally.


Nic*_*sen 11

我们实际上为此设置了一些基准数字.这个基准测试显示的是,实际上,尝试/最终的时间与调用空函数的开销一样小(可能更好地说:"跳转到下一条指令",正如IL专家所说的那样)以上).

            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;
                }
            }
Run Code Online (Sandbox Code Playgroud)

结果:33,33,32,35,32 63,64,69,66,66(毫秒,确保您有代码优化)

因此,1000万次循环中的try/finally大约需要33毫秒的开销.

每次尝试/最后,我们正在谈论0.033/10000000 =

try/finally的3.3纳秒或33亿分之一秒的开销.


Ben*_*gie 6

安德鲁巴伯说的话.除非抛出异常,否则实际的TRY/CATCH语句不会增加/忽略开销.最后没有什么特别之处.在try + catch语句中的代码完成后,您的代码总是会跳转到最后


Liv*_* M. 6

较低的水平与未达到的条件finally一样昂贵else.它实际上是汇编程序(IL)的跳转.