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

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

我们也看到了关于try / catch和exception的开销问题。

然而,我所链接的问题并没有提到只是试试最后的开销。

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

再一次,我只是想要try / finally ,没有catch ,没有抛出exception。

谢谢!

编辑:好吧,我会试图让我的使用案例更好一点。

我应该使用哪个DoWithTryFinallyDoWithoutTryFinally

 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; } } 

这种情况过于简单,因为只有两个返回点,但想象一下,如果有四个……或十个……或一百个。

在某些时候,我会想要使用try / finally ,原因如下:

  • 坚持DRY的原则(特别是出口点的数量越来越高)
  • 如果事实certificate我错了我的内部函数不抛出一个exception,那么我想确保this.Working一点。工作设置为false

所以假设,考虑到性能问题,可维护性和DRY原则,对于多less个退出点(特别是如果我假设所有内部exception都被捕获),我是否愿意承受与try / finally相关的性能损失?

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

为什么不看你实际上得到什么?

这是C#中的一个简单的代码块:

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

这里是debugging版本中产生的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 } 

以下是在debugging中运行时由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 

现在,如果我把这个尝试和最后的回报评论出来,我会从JIT得到几乎相同的程序集。 你会看到的差异是跳转到finally块,以及一些代码,以确定执行finally之后要去哪里。 所以你说的是TINY的区别。 在发行版中,跳转到最后会得到优化,大括号是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 

所以你说的是最后一个小小的花费。 这个问题很less有问题。 如果你正在做一些像memcpy这样的事情,并且在每个被拷贝的字节周围放一个try / finally,然后继续复制数百MB的数据,我可以看到这是一个问题,但是在大多数情况下呢? 微不足道。

所以让我们假设有一个开销。 那么你finally会停止使用吗? 希望不是。

IMO性能指标只有在您可以select不同的选项时才有意义。 我finally无法看到你怎么才能得到finally的语义。

try/finally是非常轻量级的。 实际上,只要不出现exception, try/catch/finally也是如此。

我有一个快速的应用程序,我刚才做了一个testing, 在一个紧密的循环中,它确实没有增加执行时间。

我会再次发布,但它很简单, 只是运行一个紧密的循环来做一些事情, try/catch/finally不会在循环中抛出任何exception,并将结果放在没有try/catch/finally

我们实际上把一些基准数字放在这里。 这个基准testing显示,实际上,try / finally的时间大约和调用一个空函数的开销一样小(可能更好的是,如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; } } 

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

因此,在千万次循环中,尝试/终于大约33毫秒的开销。

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

3.3纳秒或3.3亿分之一秒的尝试/最后的开销。

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

如果条件不符合,那么finally级别和else级别一样贵。 这实际上是一个汇编程序(IL)的跳跃。