我不想讨论何时抛出异常而不抛出异常.我想解决一个简单的问题.99%的时间不抛出异常的论点围绕着它们缓慢而另一方声称(基准测试)速度不是问题.我读过很多关于一方或另一方的博客,文章和帖子.那是哪个呢?
答案中的一些链接:Skeet,Mariani,Brumme.
我处于"不慢"的一面 - 或者更准确地说"不够慢,不值得在正常使用中避免使用它们".我写了两篇关于此的短文 .有基准方面的批评,主要是"在现实生活中需要更多的堆栈,所以你要吹掉缓存等" - 但是使用错误代码在堆栈上工作也会打击缓存,所以我不认为这是一个特别好的论点.
只是为了说清楚 - 我不支持在不符合逻辑的情况下使用例外.例如,int.TryParse
完全适合于转换用户的数据.它在读取机器生成的文件时是合适的,其中失败意味着"文件不是它的格式,我真的不想尝试处理这个,因为我不知道还有什么可能是错的. "
在"只有合理的情况"中使用例外情况时,我从未见过一个应用程序,其性能因异常而受到严重影响.基本上,除非你有明显的正确性问题,否则不应经常发生异常,如果你有明显的正确性问题,那么性能不是你面临的最大问题.
从实施它们的人那里得到了明确的答案 - 克里斯布鲁姆.他写了一篇关于这个主题的优秀博客文章(警告 - 很长)(警告2 - 写得非常好,如果你是技术人员,你会把它读到最后,然后不得不在下班后弥补你的工作时间:) )
执行摘要:它们很慢.它们被实现为Win32 SEH异常,因此有些甚至会传递0 0 CPU边界!显然在现实世界中,你将做很多其他的工作,所以奇怪的例外根本不会被注意到,但是如果你将它们用于程序流程,除了你的应用程序被锤击.这是MS营销机器给我们带来伤害的另一个例子.我记得有一个微软告诉我们他们如何产生绝对零开销,这是完全的tosh.
克里斯给出了一个相关的引用:
事实上,CLR内部甚至在引擎的非托管部分使用异常.但是,有一个严重的长期性能问题,例外情况,这必须考虑到您的决定.
我不知道人们说什么,只有当他们被抛出时他们说他们很慢.
编辑:如果没有抛出异常,那么这意味着你正在做新的异常()或类似的东西.否则异常将导致线程被挂起,并且堆栈将被移动.在较小的情况下这可能是好的,但在高流量的网站中,依赖异常作为工作流或执行路径机制肯定会导致性能问题.例外情况本身并不错,对表达特殊情况很有用
.NET应用程序中的异常工作流使用第一次和第二次机会异常.对于所有异常,即使您正在捕获并处理它们,仍然会创建异常对象,并且框架仍然必须遍历堆栈以查找处理程序.如果你抓住并重新抛出当然需要更长的时间 - 你将获得第一次机会异常,抓住它,重新抛出它,造成另一个第一次机会异常,然后找不到处理程序,然后导致第二次机会例外.
例外也是堆上的对象 - 因此,如果您抛出大量异常,那么就会导致性能和内存问题.
此外,根据ACE团队撰写的"性能测试Microsoft .NET Web应用程序"副本:
"异常处理很昂贵.当CLR通过调用堆栈递归搜索正确的异常处理程序时,所涉及的线程的执行被暂停,当找到它时,异常处理程序和一些finally块必须都有机会执行在进行常规处理之前."
我在该领域的经验表明,减少异常有助于提高绩效.当然,在性能测试时还要考虑其他一些因素 - 例如,如果您的磁盘I/O被拍摄,或者您的查询是在几秒钟内,那么这应该是您的重点.但是,查找和删除异常应该是该策略的重要组成部分.
我理解的论点并不是说抛出异常本身就很慢.相反,它是关于使用throw/catch构造作为控制正常应用程序逻辑的第一类方法,而不是更传统的条件构造.
通常在正常的应用程序逻辑中,您执行循环,其中相同的操作重复数千/数百万次.在这种情况下,通过一些非常简单的分析(参见秒表类),您可以自己看到抛出异常而不是说简单的if语句可能会变得非常慢.
事实上,我曾经读到微软的.NET团队将.NET 2.0中的TryXXXXX方法引入许多基本FCL类型,因为客户抱怨他们的应用程序性能太慢.
事实证明,在许多情况下,这是因为客户在循环中尝试对值进行类型转换,并且每次尝试都失败了.抛出转换异常,然后由异常处理程序捕获,然后吞并异常并继续循环.
Microsoft现在建议在这种情况下应特别使用TryXXX方法以避免此类可能的性能问题.
我可能错了,但听起来你不确定你所读到的"基准"的真实性.简单的解决方案:亲自尝试一下.