我经常看到人们说异常很慢,但我从来没有看到任何证据。所以,我不会问它们是否慢,而是会问异常在幕后是如何工作的,这样我就可以决定何时使用它们,以及它们是否慢。
据我所知,异常和多次返回是一样的,只是它在每次返回后都会检查是否需要再返回一次或者停止返回。它是如何检查何时停止返回的呢?我猜还有第二个堆栈,它保存了异常的类型和堆栈位置。然后它会返回,直到到达那里。我还猜测,第二个堆栈唯一被触及的时间是在一次抛出和每次尝试/捕获时。AFAICT用返回代码实现一个类似的行为也会花费同样的时间。但这一切都只是猜测,所以我想知道真正发生了什么。
异常实际上是如何工作的?
7条答案
按热度按时间bvjveswy1#
我决定用一小段C++代码和一个有点旧的Linux安装来实际查看生成的代码,而不是猜测。
我用
g++ -m32 -W -Wall -O3 -save-temps -c
编译了它,并查看了生成的汇编文件。_ZN11MyExceptionD1Ev
是MyException::~MyException()
,因此编译器决定需要析构函数的非内联副本。令人惊讶的是!在正常的代码路径上根本没有额外的指令。相反,编译器生成了额外的外部修复代码块,通过函数末尾的表引用(实际上放在可执行文件的单独部分)。所有工作都是由标准库基于这些表(
_ZTI11MyException
是typeinfo for MyException
)在后台完成的。好吧,这对我来说并不奇怪,我已经知道这个编译器是怎么做的了。继续看汇编输出:
这里我们看到了抛出异常的代码。虽然不会因为可能抛出异常而产生额外的开销,但实际抛出和捕获异常显然会产生大量开销。大部分开销隐藏在
__cxa_throw
中,__cxa_throw
必须:将其与简单返回一个值的成本进行比较,您就会明白为什么异常应该只用于异常返回。
最后,程序集文件的其余部分:
类型信息数据。
甚至更多的异常处理表和各种各样的额外信息。
所以,结论,至少对于Linux上的GCC:代价是额外的空间(用于处理程序和表),无论是否抛出异常,加上在抛出异常时解析表和执行处理程序的额外代价。2如果你使用异常而不是错误代码,并且很少出现错误,那么它可以 * 更快 *,因为你不再有测试错误的开销。
如果你想了解更多信息,特别是所有
__cxa_
函数的功能,请参阅它们的原始规范:7ivaypg92#
异常是缓慢的在过去是真实的。
在大多数现代编译器中,这不再适用。
注:我们有异常并不意味着我们不使用错误代码。当错误可以在本地处理时,请使用错误代码。当错误需要更多上下文才能更正时,请使用异常:我在这里写得更有说服力:指导您的异常处理策略的原则是什么?
当没有使用异常时,异常处理代码的开销实际上为零。
当抛出异常时,就完成了一些工作。
但是你必须把它和返回错误代码并一直检查到错误可以被处理的地方的成本进行比较,两者都需要更多的时间来编写和维护。
此外,对于新手来说,还有一个陷阱:
虽然Exception对象应该很小,但有些人在里面放了很多东西。然后你就有了复制Exception对象的成本。解决方法有两个:
在我看来,我敢打赌,相同的代码有异常是更有效的或至少是可比的代码没有异常(但有所有额外的代码来检查函数错误的结果).记住,你不是免费得到任何东西编译器是生成代码,你应该写在第一位来检查错误代码(通常编译器是比人类更有效).
lfapxunr3#
有很多方法可以实现异常,但通常它们依赖于操作系统的一些底层支持。在Windows上,这是结构化异常处理机制。
关于代码项目的细节有体面的讨论:How a C++ compiler implements exception handling
异常开销的产生是因为编译器必须生成代码来跟踪当异常传播到作用域之外时,每个堆栈帧(或者更准确地说,作用域)中哪些对象必须被析构。如果一个函数在堆栈上没有需要调用析构函数的局部变量,那么它在异常处理方面不应该有性能损失。
使用返回代码一次只能展开堆栈的一个级别,而异常处理机制如果在中间堆栈帧中没有任何事情可做,则可以在一个操作中沿堆栈向下跳转得更远。
kgqe7b3p4#
Matt Pietrek在Win32 Structured Exception Handling上写了一篇优秀的文章,虽然这篇文章最初写于1997年,但它仍然适用于今天(但当然只适用于Windows)。
cuxqih215#
This article研究了这个问题,基本上发现在实践中,异常是有运行时开销的,尽管如果不抛出异常,开销相当低。
1szpjjfi6#
几年前,我的一个朋友写了一些Visual C++如何处理异常的文章。
http://www.xyzw.de/c160.html
j5fpnvbx7#
答案都很好。
另外,考虑一下在方法的顶部将“if检查”作为门来调试代码比允许代码抛出异常要容易得多。
我的座右铭是:写好代码很容易。最重要的是为下一个看到它的人写代码。在某些情况下,9个月后就是你了,你不想诅咒自己的名字吧!