try catch performance
这篇关于msdn的文章指出,您可以使用任意多的try-catch块,只要不引发实际的异常,就不会产生任何性能成本。因为我一直认为,即使不抛出异常,一个try-catch也会受到一个小的性能影响,所以我做了一个小测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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)); } |
我得到的输出:
1 2 | With try catch: 68 Without try catch: 34 |
所以,使用no-try-catch块似乎更快?更奇怪的是,当我将for循环体中的计算替换为更复杂的计算时,比如:
1 2 | With try catch: 640 Without try catch: 655 |
我是在做错事还是有合理的解释?
JIT不会对"受保护的"/"Try"块执行优化,我想这取决于您在Try/Catch块中编写的代码,这将影响您的性能。
Try/Catch/Finally/Fault块本身在优化的发布程序集中基本上没有开销。虽然catch和finally块经常会添加额外的IL,但如果不抛出异常,则行为上几乎没有差异。而不是简单的复试,通常有一个休假到稍后的复试。
Try/Catch/Finally块的真正成本发生在处理异常时。在这种情况下,必须创建异常,必须放置堆栈爬行标记,如果处理异常并访问其StackTrace属性,则会发生堆栈遍历。最重的操作是堆栈跟踪,它遵循先前设置的堆栈爬行标记来构建一个StackTrace对象,该对象可用于显示发生错误的位置及其冒泡通过的调用。
如果在Try/Catch块中没有任何行为,那么"离开RET"与"只剩下RET"的额外成本将占主导地位,并且明显存在可测量的差异。但是,在Try子句中存在某种行为的任何其他情况下,块本身的成本都将被完全否定。
请注意,我只提供单声道:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 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 ; } } |
拆卸这些:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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 |
和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 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 |
我看到的主要区别是a.cs在
实际的计算量是如此之小,以至于精确的测量非常困难。在我看来,Try-Catch可能会给程序增加一点固定的额外时间。我冒昧地猜测,在不知道C中如何实现异常的情况下,这主要是对异常路径的初始化,也许只是对JIT的一个轻微负载。
对于任何实际使用,花费在计算上的时间将压倒花在处理try catch上的时间,因此,try catch的成本可以被视为接近零。
问题首先出现在测试代码中。您使用了秒表.elapsed.millises,它只显示经过时间的毫秒部分,使用totalms获取整个部分…
如果不引发异常,则差异最小
但真正的问题是"我需要检查异常还是让C处理异常抛出?"
显然…独自处理…尝试运行此:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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)); } |
输出:过时:看下面!带Try-Catch:1.9938401
无尝试捕获:8.92E-05
令人惊奇的是,只有10000个物体,除了200个例外。
更正:我在调试时运行代码,并将异常写入输出窗口。这些是发布的结果开销大大减少,但仍有7500%的改进。
带Try-Catch:0.0546915
单独检查:0.0007294
使用Try-Catch抛出自己的同一异常对象:0.0265229
对于这样的测试,仅仅34毫秒的差异就小于误差范围。
正如您所注意到的,当您增加测试持续时间时,差异就消失了,两组代码的性能实际上是相同的。
在进行这种基准测试时,我尝试在每段代码上循环至少20秒,最好是更长的时间,理想情况下是几个小时。
有关如何使用Try/Catch块的讨论,请参见Try/Catch实现的讨论,有些实现的开销很高,有些实现的开销为零,当没有异常发生时。