C#中“尝试”的性能成本

我知道exception有一个性能损失,而且试图避免exception通常比在任何东西上放下一个大的try / catch都更有效 – 但是try块本身呢? 只是声明一个try / catch的代价是什么,即使它从不抛出exception呢?

尝试的性能成本非常小。 exception处理的主要成本是获取堆栈跟踪和其他元数据,这是一个成本,直到你真的不得不抛出一个exception。

但是,这将因语言和实施而有所不同。 为什么不用C#编写一个简单的循环,并自己动手呢?

其实,几个月前,我正在创build一个ASP.NET Web应用程序,我不小心用很长的循环包装了一个try / catch块。 即使循环没有产生每一个例外,但花费太多时间才能完成。 当我回去看到循环所包含的try / catch时,我用另一种方式做了它,我把循环包装在try / catch块中。 性能提高了很多。 你可以自己尝试:做一些类似的事情

int total; DateTime startTime = DateTime.Now; for(int i = 0; i < 20000; i++) { try { total += i; } catch { // nothing to catch; } } Console.Write((DateTime.Now - startTime).ToString()); 

然后拿出try / catch块。 你会看到一个很大的区别!

一个常见的说法是,被捕的exception是昂贵的,而不是抛出。 这是因为大多数exception元数据收集(例如获取堆栈跟踪等)只能在try-catch端(而不是在抛出端)发生。

实际上,展开堆栈实际上是相当快速的 – CLR走出调用堆栈,只注意它find的finally块; 在纯粹的try-finally块中没有任何一点运行时试图“完成”一个exception(它是元数据等)。

从我所记得的情况来看,任何带有filter的尝试(比如“catch(FooException){}”)都是非常昂贵的 – 即使它们对exception没有任何帮助。

我敢冒险说,一个方法(称之为CatchesAndRethrows)与下面的块:

 try { ThrowsAnException(); } catch { throw; } 

可能导致在一个方法中更快的栈走 – 例如:

 try { CatchesAndRethrows(); } catch (Exception ex) // The runtime has already done most of the work. { // Some fancy logic } 

有些数字:

 With: 0.13905ms Without: 0.096ms Percent difference: 144% 

这里是我跑的基准(记住,释放模式 – 没有debugging运行):

  static void Main(string[] args) { Stopwatch withCatch = new Stopwatch(); Stopwatch withoutCatch = new Stopwatch(); int iterations = 20000; for (int i = 0; i < iterations; i++) { if (i % 100 == 0) { Console.Write("{0}%", 100 * i / iterations); Console.CursorLeft = 0; Console.CursorTop = 0; } CatchIt(withCatch, withoutCatch); } Console.WriteLine("With: {0}ms", ((float)(withCatch.ElapsedMilliseconds)) / iterations); Console.WriteLine("Without: {0}ms", ((float)(withoutCatch.ElapsedMilliseconds)) / iterations); Console.WriteLine("Percent difference: {0}%", 100 * withCatch.ElapsedMilliseconds / withoutCatch.ElapsedMilliseconds); Console.ReadKey(true); } static void CatchIt(Stopwatch withCatch, Stopwatch withoutCatch) { withCatch.Start(); try { FinallyIt(withoutCatch); } catch { } withCatch.Stop(); } static void FinallyIt(Stopwatch withoutCatch) { try { withoutCatch.Start(); ThrowIt(withoutCatch); } finally { withoutCatch.Stop(); } } private static void ThrowIt(Stopwatch withoutCatch) { throw new NotImplementedException(); } 

要看看它真的花了多less钱,你可以运行下面的代码。 它需要一个简单的二维数组,并生成超出范围的随机坐标。 如果你的例外只发生一次,当然你不会注意到它。 我的例子就是强调这样做几千次就意味着什么,捕捉一个exception与执行一个简单的testing将会为你节省。

  const int size = 1000; const int maxSteps = 100000; var randomSeed = (int)(DateTime.UtcNow - new DateTime(1970,1,1,0,0,0).ToLocalTime()).TotalMilliseconds; var random = new Random(randomSeed); var numOutOfRange = 0; var grid = new int[size,size]; var stopwatch = new Stopwatch(); Console.WriteLine("---Start test with exception---"); stopwatch.Reset(); stopwatch.Start(); for (int i = 0; i < maxSteps; i++) { int coord = random.Next(0, size * 2); try { grid[coord, coord] = 1; } catch (IndexOutOfRangeException) { numOutOfRange++; } } stopwatch.Stop(); Console.WriteLine("Time used: " + stopwatch.ElapsedMilliseconds + "ms, Number out of range: " + numOutOfRange); Console.WriteLine("---End test with exception---"); random = new Random(randomSeed); stopwatch.Reset(); Console.WriteLine("---Start test without exception---"); numOutOfRange = 0; stopwatch.Start(); for (int i = 0; i < maxSteps; i++) { int coord = random.Next(0, size * 2); if (coord >= grid.GetLength(0) || coord >= grid.GetLength(1)) { numOutOfRange++; continue; } grid[coord, coord] = 1; } stopwatch.Stop(); Console.WriteLine("Time used: " + stopwatch.ElapsedMilliseconds + "ms, Number out of range: " + numOutOfRange); Console.WriteLine("---End test without exception---"); Console.ReadLine(); 

此代码的输出示例:

 ---Start test with exception--- Time used: 3228ms, Number out of range: 49795 ---End test with exception--- ---Start test without exception--- Time used: 3ms, Number out of range: 49795 ---End test without exception--- 

您可能需要阅读结构化exception处理。 这是Window的exception实现,在.NET中使用。

http://www.microsoft.com/msj/0197/Exception/Exception.aspx