为什么抛出异常如此缓慢?

3
他们告诉我们不要使用异常来控制程序流程,因为抛出异常会很慢。我从未听说过为什么抛出异常会很慢的解释。
所以问题是:
抛出异常的机制是什么?哪些特定操作可能会影响性能?
编辑:
一些澄清:我想知道操作系统需要处理抛出异常的额外工作。是否存在用户和内核模式之间的切换,这些切换很耗费资源?或者构建异常对象很昂贵?或者在程序流程切换方面我漏了什么?我的问题与编程语言无关(我希望如此,但请指出我错误)。然而,如果你需要一些锚点,那么我最感兴趣的是与这个主题相关的.NET内部。
编辑2:
我对异常的性能没有任何问题。我只是想了解这种机制的内部原理。
编辑3:
让我的问题更加清晰。

2
你指的是哪种语言的哪种实现,谁说它的异常处理很慢? - Fred Foo
我所指的是一般情况下抛出异常,我想每个人都在谈论这个问题:我的学术老师、同事、老板,互联网上的每个人 :) - 0lukasz0
可能是重复的问题:.NET 异常有多慢? - paparazzo
2
如果由于异常而导致性能问题,那么问题不仅在于异常本身的速度慢,更在于你抛出了太多的异常。换句话说,如果你正确地使用异常来指示“异常问题”,那么你就不必担心性能问题。 - Lasse V. Karlsen
一句常见的说法是:如果你关心异常的性能,那么你可能正在过度使用异常。但性能仍然是一个好的讨论话题。 - seand
显示剩余3条评论
4个回答

7
异常处理需要一些复杂性和“魔法”。如@Joni所说,主要的成本是收集堆栈跟踪信息。当抛出异常时,运行时必须沿着堆栈的激活记录向下查找兼容的异常处理程序,并在每个步骤中执行finally块。所有这些都必须在运行时发生;编译器无法修复它。在像C++这样的语言中,必须执行析构函数。
异常处理实质上是一种带外“异常”处理模式。加速正常执行的事物,如缓存等,效果不佳。(我想局部引用可能会更糟)。这种处理可以进行优化,但由于异常处理应该是“特殊”的,所以它得到的关注较少。

1
最终块和析构函数对于抛出异常的开销并不比使用返回值和手动沿着调用堆栈向上移动更多。这些最终块和析构函数将无论如何执行。 - goji

4

异常是在应用程序级别创建的,操作系统不支持它们。抛出和捕获异常为什么会比调用或从函数返回等其他非本地控制转移更慢,没有特定的原因。

异常之所以比返回错误代码的替代方案“慢”,取决于编程环境的特定要求需要额外的工作量。例如,在Java中,抛出异常最慢的部分是填写堆栈跟踪。


2
使用异常来控制流程的主要问题是,我应该能够附加一个调试器并告诉它在异常情况下停止...而且它只会在发生例外情况时停止。如果代码使用异常来控制流程,并且在正常操作下经常中断,那么调试起来就变得非常困难,需要排除异常等等...但是这样可能会错过真正的例外情况。
如果我编写测试来验证正面功能,我应该能够附加调试器运行它们而不被捕获。如果无法做到这一点,你可能在滥用异常。
例如:假设我有一个获取ID项的代码。我的代码确定地发现该ID不存在项目。我应该抛出NotFoundException吗?这是一个争议点——我会说不——这里没有发生任何异常/错误。你的代码正确地发现了该ID不存在,没有出现任何查找错误。这些都是我看到异常被滥用并导致在非异常情况下抛出异常的情况。

2
你得到了错误的信息,但是你的实际代码结果可能不会改变。.NET中的异常处理系统实际上非常快,这意味着它的性能与其他错误处理选项相当。但是,正如另一个答案中提到的,你只应该适当地使用异常——即仅用于在应用程序正常运行期间不会出现的异常情况。原因如下:
  • 分析涉及异常的代码流程比不涉及异常的代码更加复杂,因此你的“正常”代码应避免抛出异常。
  • Visual Studio调试器将报告每次抛出异常的情况。虽然在输出窗口中只有一行,但如果你误用异常,则此功能将很快变成噩梦。
  • Visual Studio具有在抛出特定类型异常时中断的能力。如果你始终正确使用异常(用于其预期目的,并且仅用于指示该类型的实际异常),则此功能提供了一种快速调试与错误处理(恢复、报告等)相关的应用程序错误的方法。

当涉及处理实际的异常情况时,异常处理实际上比其他诸如返回错误代码之类的东西提供了性能优势。当涉及分支预测和提示时,JIT可以假设代码永远不会抛出异常,从而生成有效地使用处理器的任何可用分支预测功能以避免代码分支开销的代码,包括错误处理特性但未主动处理错误的代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接