.NET异常的速度有多慢?

时间:2020-03-06 15:01:21  来源:igfitidea点击:

我不想讨论何时以及不引发异常。我希望解决一个简单的问题。 99%的情况下,不引发异常的论点围绕它们的缓慢进行,而另一方声称(通过基准测试)速度不是问题。我已经阅读了许多有关某一方面的博客,文章和帖子。那是什么呢?

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

解决方案

在发布模式下,开销最小。

除非我们将以递归方式将异常用于流控制(例如,非本地出口),否则我怀疑我们是否会注意到差异。

我站在"不慢"的一边,或者更确切地说是"不够慢,不值得在正常使用中避免使用它们"。我已经写了两篇有关此的简短文章。对基准方面存在批评,这主要归结为"在现实生活中,有更多的堆栈要通过,因此我们会破坏高速缓存等",但是使用错误代码沿堆栈向上工作也将造成打击缓存,所以我认为这不是一个特别好的论点。

为了清楚起见,我不支持使用不合逻辑的异常。例如,int.TryParse完全适合转换用户的数据。当读取机器生成的文件时,这是适当的,失败表示"文件的格式不正确,我真的不想尝试处理该文件,因为我不知道还有什么问题。 "

在"仅在合理的情况下"使用例外时,我从未见过一个性能受到例外严重影响的应用程序。基本上,除非我们遇到重大的正确性问题,否则异常不应该经常发生,并且如果我们遇到重大的正确性问题,那么性能并不是我们面临的最大问题。

据我了解,并不是说抛出异常是不好的,它们本身很慢。相反,它是将throw / catch构造用作控制常规应用程序逻辑的一流方法,而不是更传统的条件构造。

通常,在正常的应用程序逻辑中,我们会执行循环,在此循环中,相同的动作会重复数千/百万次。在这种情况下,通过一些非常简单的分析(请参见Stopwatch类),我们可以亲自看到抛出异常而不是简单的if语句可能会变慢。

实际上,我曾经读过Microsoft的.NET团队将.NET 2.0中的TryXXXXX方法引入了许多基本FCL类型,特别是因为客户抱怨他们的应用程序性能太慢。

事实证明,在许多情况下,这是因为客户试图在循环中进行值的类型转换,而每次尝试均失败。引发了转换异常,然后由异常处理程序捕获了转换处理程序,然后该异常处理程序吞下了该异常并继续执行循环。

Microsoft现在建议特别在这种情况下使用TryXXX方法,以避免这种可能的性能问题。

我可能是错的,但听起来我们不确定所阅读的"基准"的准确性。简单的解决方案:自己尝试一下。

我从来没有任何性能问题,例外。我经常使用异常-如果可能的话,我永远不会使用返回码。这是一种不好的做法,在我看来,它们的气味像意大利面条代码。

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

但是,如果我们在堆栈的底部抛出并在顶部捕获(将整个返回代码链替换为一个throw / catch),那么所有昂贵的操作都将执行一次。

归根结底,它们是有效的语言功能。

只是为了证明我的观点

请在此链接上运行代码(对于答案来说太大了)。

我计算机上的结果:

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

时间戳在开始时输出,在返回码和异常之间,最后输出。两种情况都需要花费相同的时间。请注意,我们必须进行优化编译。

如果将它们与返回代码进行比较,它们会变得很慢。但是,正如先前的发帖人所述,我们不想使程序正常运行,因此只有在出现问题时才能取得成功,并且在大多数情况下,性能不再重要(因为无论如何,这都意味着障碍)。

它们绝对值得使用,而不是错误代码,其优点是巨大的IMO。

实施Chris Brumme的人对此有一个明确的答案。他撰写了一篇关于该主题的出色博客文章(警告时间很长)(警告2文章写得很好,如果我们是一个技术人员,则需要阅读到最后,然后必须下班后才花点时间:))

摘要:它们很慢。它们被实现为Win32 SEH异常,因此有些甚至会超过Ring 0 CPU边界!
显然,在现实世界中,我们将做很多其他工作,因此根本不会注意到奇怪的异常,但是如果将它们用于程序流程,则应用程序会受到重创。这是MS营销机器给我们造成损害的另一个示例。我记得一位microsoftie告诉我们他们是如何产生绝对零开销的,这完全是tosh。

克里斯给出了一个相关的报价:

In fact, the CLR internally uses
  exceptions even in the unmanaged
  portions of the engine.  However,
  there is a serious long term
  performance problem with exceptions
  and this must be factored into your
  decision.

我不知道人们说什么才慢,被抛出就在谈论什么。

编辑:如果未引发异常,则意味着我们正在执行新的Exception()或者类似的操作。否则,异常将导致线程被挂起,并且堆栈被遍历。在较小的情况下,这可能是好的,但是在高流量的网站中,依靠异常作为工作流或者执行路径机制肯定会导致性能问题。异常本身并不坏,并且对于表达异常条件很有用。

.NET应用程序中的异常工作流程使用第一次和第二次机会异常。对于所有异常,即使我们正在捕获并处理它们,仍会创建异常对象,并且框架仍必须遍历堆栈以查找处理程序。如果捕获并重新抛出当然会花费更长的时间,那么我们将获得一个优先机会异常,将其捕获并重新抛出,从而导致另一个优先机会异常,然后该异常找不到处理程序,这将导致第二次机会异常。

异常也是堆上的对象,因此,如果我们抛出大量异常,则会导致性能和内存问题。

此外,根据我由ACE团队撰写的"性能测试Microsoft .NET Web应用程序"的副本:

"异常处理非常昂贵。在搜索正确的异常处理程序时,CLR通过调用堆栈递归时,所涉及线程的执行将被挂起,并且当找到异常处理程序和一些finally块时,它们都必须有机会执行才能进行常规处理。"

我自己在该领域的经验表明,减少异常现象对性能有很大帮助。当然,在进行性能测试时,我们还需要考虑其他因素,例如,如果磁盘I / O被拍摄或者查询在几秒钟之内,那么这应该成为重点。但是,发现和消除异常应该是该策略的重要组成部分。

在我一直试图防止它们发生(例如在尝试读取更多数据之前检查套接字是否已连接)并为自己提供避免它们的方法之后,我的XMPP服务器获得了重大的速度提升(对不起,没有实际数字,纯粹是观察性的) (提到的TryX方法)。当时只有大约50个活动(聊天)虚拟用户。

在此,快速捕获与捕获异常相关的性能。

当执行路径进入" try"块时,没有任何神奇的事情发生。没有" try"指令,也没有与进入或者退出try块相关的成本。有关try块的信息存储在方法的元数据中,并且在运行时每当引发异常时就使用此元数据。执行引擎在堆栈中向下移动,以查找try块中包含的第一个调用。仅当引发异常时,才会发生与异常处理相关的所有开销。