我们已经看到很多关于何时以及为何使用try
/ catch
和try
/ catch
/ finally
的问题。我知道try
/ finally
肯定有一个用例(特别是因为它是实现using
语句的方式)。
我们也看到了关于the overhead of try/catch and exceptions的问题。
然而,我所链接的问题并没有谈到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;
}
}
这种情况过于简单,因为只有两个回归点,但想象一下是否有四个......或十个......或一百个。
在某些时候,我会想要使用try
/ finally
,原因如下:
this.Working
设置为false
。假设有性能问题,可维护性和DRY原则,指出了多少个退出点(特别是如果我可以假设所有内部异常都被捕获)我想承担与try
/ finally
相关的任何性能损失?
编辑#2:我将this.Working
的名称更改为this.IsBusy
。对不起,忘记提到这是多线程的(虽然只有一个线程实际上会调用该方法);其他线程将轮询以查看对象是否正在执行其工作。如果工作按预期进行,返回值仅仅是成功或失败。
答案 0 :(得分:94)
为什么不看看你到底得到了什么?
以下是C#中的一小段代码:
static void Main(string[] args)
{
int i = 0;
try
{
i = 1;
Console.WriteLine(i);
return;
}
finally
{
Console.WriteLine("finally.");
}
}
以下是调试版本中产生的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
}
这是在调试中运行时由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
现在,如果我注释掉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
因此,您正在谈论非常非常小的尝试/最终成本。很重要的问题领域很少。如果你正在做类似memcpy的事情,并在每个被复制的字节周围试一试,然后继续复制数百MB的数据,我可以看出这是一个问题,但在大多数情况下?可忽略的。
答案 1 :(得分:52)
所以让我们假设有一个开销。你打算暂时停止使用finally
吗?希望不是。
IMO绩效指标仅在您可以选择不同选项时才相关。我不知道如何在不使用finally
的情况下获得finally
的语义。
答案 2 :(得分:27)
try/finally
非常轻量级。实际上,只要没有抛出异常,try/catch/finally
也是如此。
我有一个快速的个人资料应用程序,我前一段时间测试它;在一个紧密的循环中,它确实没有添加任何执行时间。
我会再次发布,但这很简单;只需运行一个紧凑的循环来做一些事情,try/catch/finally
不会在循环中抛出任何异常,并将结果与没有try/catch/finally
的版本计时。
答案 3 :(得分:10)
让我们实际上为此添加一些基准数字。这个基准测试显示的是,实际上,尝试/最终的时间与调用空函数的开销一样小(可能更好地说:“跳转到下一条指令”,正如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 (毫秒,确保你有代码优化)
在 1000万循环中,try / finally的开销大约为33毫秒。
每次尝试/最后,我们正在谈论0.033 / 10000000 =
尝试/最后的3.3纳秒或33亿分之一秒的开销。
答案 4 :(得分:6)
Andrew Barber说的话。除非抛出异常,否则实际的TRY / CATCH语句不会增加/忽略开销。最后没有什么特别之处。在try + catch语句中的代码完成后,您的代码总是跳转到最终
答案 5 :(得分:6)
如果条件不满足,则finally
较低级else
与{{1}}一样昂贵。它实际上是汇编程序(IL)的跳转。