5

人们常说,由于性能不佳,您不应该将异常用于常规错误处理。我的猜测是,糟糕的性能是由于必须实例化一个新的异常对象、生成堆栈跟踪等造成的。那么为什么不使用轻量级异常呢?像这样的代码在逻辑上是合理的:

string ageDescription = "Five years old";
try {
    int age = int.Parse(ageDescription);
}
catch (Exception) {
    // Couldn't parse age; handle parse failure
}

然而,我们建议使用它TryParse来避免异常的开销。但是如果异常只是一个在线程启动时被初始化的静态对象,那么抛出异常的所有代码需要做的就是设置一个错误代码号,也许还有一个错误字符串。没有堆栈跟踪,没有新的对象实例化。这将是一个“轻量级异常”,因此使用异常的开销将大大减少。为什么我们没有这么轻量级的异常?

4

6 回答 6

4

异常对象实例化是整个案例中最小的问题。真正的性能杀手是控制流必须停止执行您的程序,并且必须在调用堆栈中查找可以捕获抛出异常的可能处理程序(捕获块),然后它必须执行正确的处理程序(以及它们的 finally 块) , 在被告知时重新抛出异常,然后在正确的位置继续执行程序,即在最后一个处理程序之后。您对“轻量级”异常的想法不会改变这一点,它甚至会减慢线程的创建速度,因为它必须创建和存储异常对象,并且会阻止按类型过滤异常,这现在是可能的。

通过使用 TryParse,您可以通过一个简单的条件子句避免所有这些,而且您实际上编写的代码更少,并且更容易阅读和推理。

例外是针对特殊情况的,在这种情况下,它们为日志/调试器提供了许多有用的信息。

于 2012-12-08T19:44:41.137 回答
4

性能下降不仅仅是因为您正在创建一个新的 Exception 对象。其中很多与发生异常时需要完成的堆栈的条件展开有关。

例如,考虑当您拥有捕获不同类型异常的异常处理程序时必须完成的工作。在堆栈中的每一点,当它从被调用者到调用者展开时,语言必须进行类型检查,不仅要查看是否可以处理异常,还要查看最合适的处理程序是什么。这本身就是一大笔开销。

如果你真的想要轻量级,你应该从你的函数中返回一个结果——这就是 Int32.TryParse() 所做的。没有堆栈展开,没有类型检查,只是一个可以轻松优化的简单条件。

编辑:需要注意的一件有趣的事情是 C# 是在 Java 之后创建的。Java 有几个有趣的构造,它们导致异常处理比我们在 C# 中看到的更复杂,即检查异常throws关键字。一种有趣的读物。我(个人)很高兴 C# 没有包含这个“特性”。我的猜测是他们将异常处理程序分叉以提高性能。在现实世界中,据我所知,很多开发人员最终只是throws exception在他们的函数声明中指定。

于 2012-12-08T19:54:00.507 回答
3

你应该int.TryParse在你的情况下使用。测试一些条件然后抛出和捕获异常更快、更易读。将异常用于异常情况,而不是用于常规验证。

于 2012-12-08T19:39:08.270 回答
3

异常的问题不仅仅是生成异常本身,老实说,这甚至不是最耗时的部分。当您抛出异常时(在创建后),它需要展开通过每个范围级别的堆栈,确定该范围是否是会捕获此异常的 try/catch 块,更新异常以指示它通过了该部分堆栈,然后拆除堆栈的该部分。然后当然还有所有finally可能需要执行的块。使Exception自身存储更少的信息并不能真正简化任何事情。

于 2012-12-08T19:44:10.463 回答
2

因为“重量级”异常提供的实用程序非常有用(哈哈)。我无法告诉你我有多频繁地希望能够在 C 语言中转储诸如堆栈跟踪之类的东西,而不必要求人们拉出调试器。

在事后(即在异常被要求捕获之后)生成诸如堆栈跟踪之类的东西是不可行的,因为一旦捕获到异常,堆栈就被展开——信息消失了。您需要有关故障点的信息;因此必须在故障点收集数据。

至于“新对象实例化”——与其他昂贵的异常功能(展开堆栈、堆栈跟踪、多个函数退出点等)相比,它是如此便宜,因此不值得担心。

于 2012-12-08T19:42:26.333 回答
1

不建议您使用TryParse,而不是Parse因为性能。如果解析有任何可能失败(例如因为它是用户生成的输入),那么解析失败并不是异常的,这是可以预料的。顾名思义,例外是针对特殊情况的。对于应该早点发现但没有发现的东西,太出乎意料以至于你无法真正继续。

如果一个函数需要一个对象,但却null被传入,那么在这种情况下,正确的做法是由方法的设计者决定。如果参数是默认值的可选覆盖,程序可以继续并使用默认值或忽略该参数。但除此之外,程序应该简单地抛出一个ArgumentNullException.

在决定是否使用异常时,性能根本不应该成为考虑因素。这是一个意图和目的的问题。它们甚至没有那么慢,当然它们比添加两个整数要慢很多倍,但我仍然可以在我老化的 Core 2 Duo 上每秒抛出 50,000 个异常。如果使用异常成为瓶颈,那么您就没有以正确的方式使用它们。

于 2012-12-08T20:09:52.760 回答