试着抓住表现

MSDN上的这篇文章指出,您可以根据需要使用尽可能多的try catch块,并且不会产生任何性能成本,因为不会引发任何实际exception。
因为我总是认为即使没有抛出exception,try-catch总是会受到很小的性能影响,所以我做了一点测试。

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

我得到的输出:

 With try catch: 68 Without try catch: 34 

所以似乎没有使用try-catch块似乎更快?

我发现更奇怪的是,当我用更复杂的东西替换for循环体中的计算时: c += i * (2 * (int)Math.Floor((double)i));
差异远远不那么显着。

 With try catch: 640 Without try catch: 655 

我在这里做错了什么,或者对此有合理的解释吗?

JIT不对’protected’/’try’块执行优化,我想根据你在try / catch块中编写的代码,这会影响你的性能。

try / catch / finally / fault块本身在优化的发布程序集中基本上没有开销。 虽然通常会为catch和finally块添加额外的IL,但是当没有抛出exception时,行为几乎没有差异。 而不是一个简单的ret,通常有一个后来退休。

处理exception时会发生try / catch / finally块的真实成本。 在这种情况下,必须创建exception,必须放置堆栈爬网标记,并且,如果处理exception并访问其StackTrace属性,则会引发堆栈遍历。 最重的操作是堆栈跟踪,它跟随先前设置的堆栈爬网标记以构建StackTrace对象,该对象可用于显示错误发生的位置以及它通过的调用。

如果try / catch块中没有任何行为,那么’leave to ret’与’ret’的额外成本将占主导地位,并且显然会有可衡量的差异。 但是,在try子句中存在某种行为的任何其他情况下,块本身的成本将被完全否定。

请注意,我只提供Mono:

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

拆解这些:

 // 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 

 // 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直接retIL_0006 ,而b.cs必须leave IL_0013IL_006 。 我最好的猜测是,在编译为机器代码时, leave是一个(相对)昂贵的跳转 – 可能是也可能不是这种情况,特别是在你的for循环中。 也就是说,try-catch没有固有的开销,但跳过 catch会有成本,就像任何条件分支一样。

实际计算非常小,以至于精确测量非常棘手。 它看起来像try catch可能会为例行程序添加一小段固定的额外时间。 我不敢猜测,不知道如何在C#中实现exception,这主要是exception路径的初始化,也许只是JIT的轻微负载。

对于任何实际使用,花在计算上的时间将压倒花费在try-catch上的时间,即try-catch的成本可以接近零。

问题首先出现在您的测试代码中。 你使用了秒表 .Elapsed.Milliseconds只显示经过时间的毫秒部分,使用TotalMilliseconds来获取整个部分……

如果没有抛出exception,则差异很小

但真正的问题是“我是否需要检查exception或让C#处理exception抛出?”

显然……单独处理…试试这个:

 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个例外。

更正:我在DEBUG和VS Writen Exception上运行我的代码到输出窗口..这些是RELEASE的结果大大减少了开销,但仍然提高了7,500%。

使用try catch: 0.0546915

单独检查: 0.0007294

使用try catch抛出我自己的exception对象: 0.0265229

请参阅有关try / catch实现的讨论,以讨论try / catch块如何工作,以及某些实现如何具有高开销,并且在没有exception发生时,一些实现零开销。

对于像这样的测试,仅34毫秒的差异小于误差范围。

正如您所注意到的那样,当您增加测试的持续时间时,差异就会消失,并且两组代码的性能实际上是相同的。

在进行这种基准测试时,我尝试将代码的每个部分循环至少20秒,最好是更长,最好是几个小时。