.NETexception有多慢?

我不想讨论什么时候可以抛出exception。 我想解决一个简单的问题。 99%的时间没有抛出exception的争论围绕着他们慢,而对方声称(与基准testing),速度不是问题。 我读过许多关于一方或另一方的博客,文章和post。 那是哪个呢?

从答案的一些链接: Skeet , Mariani , Brumme 。

我在“不慢”方面 – 或者更确切地说,“不够慢,不值得在正常使用中避免它们”。 我写了两篇关于这个短文的 文章 。 对于基准方面的批评,主要是“现实生活中会有更多的堆栈可以通过,所以你会打破caching等等” – 但是使用错误代码在堆栈中工作的同时也会吹的caching,所以我不认为这是一个特别好的论点。

只是要说清楚 – 我不支持在不合逻辑的情况下使用exception。 例如, int.TryParse完全适合于转换来自用户的数据。 阅读一个机器生成的文件是合适的,其中失败的意思是“该文件不是它的格式,我真的不想尝试处理这个,因为我不知道还有什么可能是错的。 “

在“只有合理的情况下”使用exception时,我从来没有见过一个应用程序的性能明显受到exception的影响。 基本上,例外不应该经常发生,除非你有明显的正确性问题,如果你有明显的正确性问题,那么性能不是你面临的最大的问题。

这个来自实施他们的人的确切答案是Chris Brumme。 他写了一篇很好的关于这个主题的博客文章 (警告 – 它很长)(警告2 – 写得很好,如果你是一个技术人员,你会读到最后,然后不得不弥补你的工作时间:) )

执行摘要:他们很慢。 它们被实现为Win32 SEHexception,所以有些甚至可以通过ring 0 CPU边界! 显然在现实世界中,你会做很多其他的工作,所以奇怪的exception根本不会被注意到,但是如果你使用它们来进行程序stream程,除了你的应用程序被敲打。 这是MS营销机器的另一个例子,我们不屑一顾。 我记得一个微软告诉我们他们是如何产生绝对零开销,这是完整的托什。

克里斯给出了一个相关的引语:

事实上,即使在引擎的非托pipe部分,CLR也会内部使用exception。 但是,有一个严重的长期性能问题,这个问题必须考虑到你的决定中。

我不知道人们在说什么时候说他们只有被抛出才慢。

编辑:如果不引发exception,那么这意味着你正在做新的exception()或类似的东西。 否则,exception将导致线程被挂起,堆栈被执行。 在较小的情况下这可能是正确的,但是在高stream量的网站中,依靠作为工作stream程或执行path机制的exception,肯定会导致性能问题。 例外情况本身并不差,对于expression特殊的条件很有用

.NET应用程序中的exception工作stream使用第一次和第二次机会exception。 对于所有的exception,即使你正在捕获和处理它们,exception对象仍然是被创build的,框架仍然需要遍历栈来寻找处理程序。 如果你捕捉并重新抛出当然会花费更长的时间 – 你将得到一个第一次机会exception,抓住它,重新抛出它,导致另一个一次机会的exception,然后找不到处理程序,然后导致第二次机会例外。

exception也是堆上的对象 – 所以如果你抛出大量的exception,那么你同时导致性能和内存问题。

此外,根据ACE团队编写的“性能testingMicrosoft .NET Web应用程序”的副本:

“exception处理是昂贵的,当CLR通过调用堆栈search正确的exception处理程序时,挂起的线程的执行被暂停,当find时,exception处理程序和一些finally块都必须有机会执行在进行正常的处理之前“。

我在这个领域的经验表明,减less例外显着帮助了绩效。 当然,在进行性能testing时,还有其他一些因素需要考虑 – 例如,如果您的磁盘I / O出现故障,或者您的查询在几秒钟内,那么您应该关注这个问题。 但是find并消除例外情况应该是该战略的重要组成部分。

根据我的理解,这个说法并不是说抛出exception是坏的,它本身就是慢的。 相反,它将使用throw / catch结构作为控制正常应用程序逻辑的第一类方法,而不是更传统的条件结构。

通常在正常的应用程序逻辑中,您执行循环,其中相同的操作重复数千次/数百万次。 在这种情况下,通过一些非常简单的分析(请参阅Stopwatch类),您可以看到抛出exception,而不是说简单的if语句会变得非常慢。

事实上,我曾经读过微软的.NET团队将.NET 2.0中的TryXXXXX方法引入许多基本的FCLtypes,因为客户抱怨说他们的应用程序的性能太慢了。

事实certificate,在许多情况下,这是因为客户正在尝试在循环中对值进行types转换,并且每次尝试均失败。 抛出了一个转换exception,然后被exception处理程序捕获,然后吞下exception并继续循环。

微软现在build议在这种情况下应特别使用TryXXX方法来避免这种可能的性能问题。

我可能是错的,但是听起来你并不确定你所读到的“基准”的真实性。 简单的解决scheme:自己尝试一下。

在我一直试图阻止它们发生(例如在尝试读取更多数据之前检查套接字是否连接)之后,我的XMPP服务器获得了主要的速度提升(对不起,没有实际的数字,纯观察) (提到的TryX方法)。 那只有大约50个活跃(聊天)的虚拟用户。

例外情况我从来没有任何性能问题。 我使用了很多例外 – 如果可以,我从不使用返回码。 他们是一个不好的做法,在我看来,味道像意大利面代码。

我认为这一切都归结为你如何使用exception:如果你使用它们像返回代码(堆栈中的每个方法调用捕获和重新抛出),那么,是的,它们将会很慢,因为你有每个捕获/抛出的开销。

但是,如果你扔在堆栈的底部,并捕捉到顶部(用一个throw / catch代替整个返回代码链),所有昂贵的操作都会执行一次。

在一天结束时,他们是一个有效的语言function。

只是为了certificate我的观点

请在这个链接运行代码 (太大了答案)。

结果在我的电脑上:

marco@sklivvz:~/develop/test$ mono Exceptions.exe | grep PM
10/2/2008 2:53:32 PM
10/2/2008 2:53:42 PM
10/2/2008 2:53:52 PM

时间戳在开始时输出,在返回代码和例外之间输出。 在这两种情况下都需要相同的时间。 请注意,您必须进行优化编译。

如果你比较他们返回的代码,他们是缓慢的地狱。 然而,以前的海报表示,你不想投入正常的程序操作,所以只有在出现问题时才能得到性能提升,在绝大多数情况下,性能不再重要(因为例外意味着无论如何都是障碍)。

他们肯定值得使用错误代码,优势是巨大的国际海事组织。

只是为了增加我自己最近的经验来讨论这个问题:根据大部分上面写的内容,我发现即使没有运行debugging器,在重复的基础上抛出exception也是非常缓慢的。 我只是通过改变大约五行代码,将一个正在编写的大型程序的性能提高了60%:切换到返回代码模型,而不是抛出exception。 当然,有问题的代码运行了数千次,并且在我改变它之前可能会抛出数千个exception。 所以我同意上面的说法:当某些重要的事情发生错误时抛出exception,而不是在任何“预期的”情况下控制应用程序stream。

但单声道抛出exception比.net独立模式快10倍,.net独立模式比.netdebugging模式抛出exception快60倍。 (testing机具有相同的CPU型号)

 int c = 1000000; int s = Environment.TickCount; for (int i = 0; i < c; i++) { try { throw new Exception(); } catch { } } int d = Environment.TickCount - s; Console.WriteLine(d + "ms / " + c + " exceptions"); 

在释放模式下,开销很小。

除非你要以recursion方式使用stream控制的exception(例如非本地出口),否则我怀疑你将会注意到这种差异。

在Windows CLR上,对于深度为8的调用链,抛出exception比检查和传播返回值慢750倍。 (见下面的基准)

这种exception的高成本是因为Windows CLR与Windows结构化exception处理 ( Windows Structured Exception Handling)集成在一起。 这使exception能够在不同的运行时和语言中正确捕获和抛出。 但是,这非常慢。

Mono运行时(在任何平台上)的exception速度要快得多,因为它不能与SEH集成。 但是,在跨越多个运行时间传递exception时会有function损失,因为它不使用像SEH这样的东西。

这里是我的基准例外与Windows CLR的返回值的缩写结果。

 baseline: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 13.0007 ms baseline: recurse_depth 8, error_freqeuncy 0.25 (0), time elapsed 13.0007 ms baseline: recurse_depth 8, error_freqeuncy 0.5 (0), time elapsed 13.0008 ms baseline: recurse_depth 8, error_freqeuncy 0.75 (0), time elapsed 13.0008 ms baseline: recurse_depth 8, error_freqeuncy 1 (0), time elapsed 14.0008 ms retval_error: recurse_depth 5, error_freqeuncy 0 (0), time elapsed 13.0008 ms retval_error: recurse_depth 5, error_freqeuncy 0.25 (249999), time elapsed 14.0008 ms retval_error: recurse_depth 5, error_freqeuncy 0.5 (499999), time elapsed 16.0009 ms retval_error: recurse_depth 5, error_freqeuncy 0.75 (999999), time elapsed 16.001 ms retval_error: recurse_depth 5, error_freqeuncy 1 (999999), time elapsed 16.0009 ms retval_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 20.0011 ms retval_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 21.0012 ms retval_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 24.0014 ms retval_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 24.0014 ms retval_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 24.0013 ms exception_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 31.0017 ms exception_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 5607.3208 ms exception_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 11172.639 ms exception_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 22297.2753 ms exception_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 22102.2641 ms 

这是代码

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { public class TestIt { int value; public class TestException : Exception { } public int getValue() { return value; } public void reset() { value = 0; } public bool baseline_null(bool shouldfail, int recurse_depth) { if (recurse_depth <= 0) { return shouldfail; } else { return baseline_null(shouldfail,recurse_depth-1); } } public bool retval_error(bool shouldfail, int recurse_depth) { if (recurse_depth <= 0) { if (shouldfail) { return false; } else { return true; } } else { bool nested_error = retval_error(shouldfail,recurse_depth-1); if (nested_error) { return true; } else { return false; } } } public void exception_error(bool shouldfail, int recurse_depth) { if (recurse_depth <= 0) { if (shouldfail) { throw new TestException(); } } else { exception_error(shouldfail,recurse_depth-1); } } public static void Main(String[] args) { int i; long l; TestIt t = new TestIt(); int failures; int ITERATION_COUNT = 1000000; // (0) baseline null workload for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; DateTime start_time = DateTime.Now; t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { bool shoulderror = (i % EXCEPTION_MOD) == 0; t.baseline_null(shoulderror,recurse_depth); } double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; Console.WriteLine( String.Format( "baseline: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", recurse_depth, exception_freq, failures,elapsed_time)); } } // (1) retval_error for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; DateTime start_time = DateTime.Now; t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { bool shoulderror = (i % EXCEPTION_MOD) == 0; if (!t.retval_error(shoulderror,recurse_depth)) { failures++; } } double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; Console.WriteLine( String.Format( "retval_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", recurse_depth, exception_freq, failures,elapsed_time)); } } // (2) exception_error for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; DateTime start_time = DateTime.Now; t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { bool shoulderror = (i % EXCEPTION_MOD) == 0; try { t.exception_error(shoulderror,recurse_depth); } catch (TestException e) { failures++; } } double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; Console.WriteLine( String.Format( "exception_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", recurse_depth, exception_freq, failures,elapsed_time)); } } } } } 

这里有一个关于捕捉exception性能的简要说明。

当执行path进入一个“尝试”块时,没有什么不可思议的事情发生。 没有“try”指令,并且没有与进入或退出try块有关的成本。 有关try块的信息存储在方法的元数据中,每当引发exception时,都会在运行时使用此元数据。 执行引擎沿着堆栈走下去,寻找包含在try块中的第一个调用。 任何与exception处理相关的开销只有在抛出exception时才会发生。

在为他人编写课程/function时,如果例外情况合适,似乎很难说。 有一些BCL有用的部分,我不得不沟通,因为他们抛出exception,而不是返回错误。 对于某些情况,您可以解决这个问题,但是对于System.Management和Performance Counters等其他人来说,还有一些用法,您需要在BCL中频繁抛出exception。

如果你正在编写一个库,并且你的函数可能在一个循环中被使用,并且有可能需要大量的迭代,那么可以使用Try ..模式或者其他方式来暴露除了exception之外的错误。 即使如此,如果共享环境中的许多进程正在使用函数,则很难说出函数的多less。

在我自己的代码中,只有当事情非常特殊时才会抛出exception,以至于必须查看堆栈跟踪,看看出了什么问题,然后修复它。 所以我几乎已经重新编写BCL的一部分来使用基于Try ..模式的error handling而不是exception。