Performance Cost Of 'try' in C#
我知道异常会造成性能损失,而且尝试和避免异常通常比在所有事情上放弃一个大的尝试/捕获更有效——但是Try块本身呢?仅仅声明一个尝试/捕获的代价是什么,即使它从不抛出异常?
试用的性能成本很低。异常处理的主要成本是获取堆栈跟踪和其他元数据,而这是一个直到您实际必须抛出异常时才支付的成本。
但这将因语言和实现而异。为什么不用C语言写一个简单的循环,然后自己计时呢?
实际上,几个月前我正在创建一个ASP.NET Web应用程序,我不小心用一个很长的循环包装了一个try/catch块。尽管循环并没有生成所有异常,但完成它却花费了太多时间。当我返回并看到try/catch被循环包围时,我反过来做了,我将循环包围在try/catch块中。性能提高了很多。你可以自己试试这个:做一些像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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块。你会看到很大的不同!
一个常见的说法是,异常在被捕获时是昂贵的,而不是抛出。这是因为大多数异常元数据收集(例如获取堆栈跟踪等)实际上只发生在try-catch端(而不是throw端)。
展开堆栈实际上很快——clr会向调用堆栈走去,只注意找到的最后一个块;在纯try finally块中,运行时不会尝试"完成"异常(元数据等)。
根据我的记忆,任何带有过滤器的尝试捕获(例如"catch(fooexception)")都是一样昂贵的-即使它们不做任何异常。
我冒昧地说,有以下块的方法(称为catchesandrethrows):
1 2 3 4 5 6 7 8 | try { ThrowsAnException(); } catch { throw; } |
可能导致在方法中更快的堆栈遍历-例如:
1 2 3 4 5 6 7 8 | try { CatchesAndRethrows(); } catch (Exception ex) // The runtime has already done most of the work. { // Some fancy logic } |
一些数字:
1 2 3 | With: 0.13905ms Without: 0.096ms Percent difference: 144% |
以下是我运行的基准(记住,发布模式-不调试运行):
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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(); } |
要了解它真正的成本,您可以运行下面的代码。它需要一个简单的二维数组并生成超出范围的随机坐标。如果您的异常只发生一次,当然您不会注意到它。我的示例是为了强调在进行几千次这样的操作时它意味着什么,以及捕获异常与实现简单测试相比将节省您的时间。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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(); |
此代码的示例输出:
1 2 3 4 5 6 | ---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--- |
您可能希望了解结构化异常处理。它是Window对异常的实现,并在.NET中使用。
http://www.microsoft.com/msj/0197/exception/exception.aspx