2

他们告诉我们不要使用异常来控制程序的流程,因为抛出异常很慢。我从来没有听过任何解释为什么抛出异常这么慢。

所以问题是:

抛出异常的机制是什么?涉及哪些可能会影响性能的特定操作?

编辑:

一些澄清:我想听听操作系统需要什么额外的工作来处理抛出异常。用户模式和内核模式之间是否有一些昂贵的切换?或者也许构造异常对象是昂贵的?或者,也许我缺少切换程序流程的东西?我的问题与编程语言无关(我希望如此,但证明我错了)。但是,如果您需要一些锚点,那么我对与该主题相关的 .NET 内部最感兴趣。

编辑2:

我对异常性能没有任何问题。我只是想了解这种机制的内部结构。

编辑3:

让我的问题更清楚了。

4

4 回答 4

6

异常处理需要一些复杂性和“魔法”。支持@Joni 的响应的主要成本是收集堆栈跟踪。当抛出异常时,运行时必须沿着堆栈的激活记录查找兼容的异常处理程序,执行 finally 会阻塞每一步。所有这些都必须在运行时发生;它不能由编译器修复。在像 C++ 这样的语言中,必须执行析构函数。

异常处理本质上是一种带外“异常”处理模式。加速正常执行的东西(例如缓存)也不起作用。(我想这里的参考位置要差得多)。这种处理可以进行优化,但由于 exc 处理应该是“特殊的”,因此受到的关注较少。

于 2013-06-29T15:40:53.783 回答
4

在应用程序级别创建异常;他们没有操作系统支持。没有什么特别的理由说明为什么抛出和捕获异常应该比任何其他非本地控制转移慢,比如调用或从函数返回。

是什么让异常比返回错误代码的替代“慢”取决于编程环境的细节所需的额外工作。例如,在 Java 中,抛出异常最慢的部分是填充堆栈跟踪。

于 2013-06-29T13:56:02.443 回答
2

使用异常进行流控制是一个坏主意的主要原因是我应该能够附加一个调试器并告诉它在异常时中断......并且它会在例外情况下中断。如果代码使用异常进行流控制并且它在正常操作下不断中断,那么在不排除异常等情况下很难调试......但是也许你最终会错过一个真正的例外情况。

如果我编写验证积极功能的测试,我应该能够在附加调试器的情况下运行它们并且它不会捕获。如果你不能这样做,你可能会滥用异常。

一个例子:假设我有代码通过 id 获取项目。我的代码最终发现该 id 的项目不存在。我应该抛出 NotFoundException 吗?这是一个争论点——我会说不——这里没有发生任何异常/错误。您的代码正确地没有发现该 id 存在,并且没有发现错误。这些是我看到异常被滥用并导致在非异常情况下抛出异常的情况。

于 2013-06-29T16:38:50.533 回答
2

您被误导了,但是您的实际代码的结果可能不会改变。.NET 中的异常处理系统实际上非常快,这意味着它的性能可以与其他错误处理选项相媲美。但是,就像另一个答案中提到的那样,您只想适当地使用异常 - 即仅适用于您不希望在应用程序正常运行期间发生的异常情况。原因如下:

  • 分析涉及异常的代码流比不涉及异常的代码更复杂,所以你的“正常”代码应该避免抛出异常。
  • 每次引发异常时,Visual Studio 调试器都会报告。它只是输出窗口中的一行,但如果您滥用异常,那么这个美妙的功能将很快变成一场噩梦。
  • Visual Studio 能够在引发特定类型的异常时中断。如果您始终正确地使用异常(出于预期目的,并且仅用于指示该类型的实际异常),则此功能提供了一种快速调试应用程序中与错误处理(恢复、报告等)相关的错误的方法。

在处理实际异常情况时,异常处理实际上比返回错误代码等其他事情提供了性能优势。当涉及到分支预测和提示时,JIT 可以假设代码永远不会抛出异常,从而允许它生成有效使用处理器的任何可用分支预测功能的代码,以避免包含错误处理功能的代码的分支开销但没有积极处理错误。

于 2013-06-29T16:49:09.163 回答