使用 try-catch 块会降低性能吗?

48
这个链接说明:

为了捕获异常,我们必须将代码片段放在异常检查下。这可以通过将该代码片段置于 try 块中来实现。当该块内发生异常情况时,会抛出一个异常,从而将控制权转移到异常处理程序。如果没有抛出异常,则代码将正常执行,所有处理程序将被忽略。

那么,这是不是意味着使用 try 块会因为运行时的“检查”任务而降低性能呢?

通常是这样,但除非它是代码中时间关键、被调用了无数次、必须非常快的部分,否则我不会根据这个来决定是否使用异常。 - Luchian Grigore
(请注意,与Java/C#/Python不同,异常处理方式取决于您使用的编译器。)(然而,在没有抛出异常的情况下,良好的try/catch异常几乎没有0或0开销。) - Patashu
这可能会回答你的问题:https://dev59.com/hnI-5IYBdhLWcg3wW2-p#1897979 - Jeremy Friesner
4
相对于什么?是采用其他方式处理异常情况(如果是,如何处理?)还是不处理异常情况,希望它永远不会发生。 - CB Bailey
更好的重复问题,我的看法:https://dev59.com/K2Yr5IYBdhLWcg3wQH-- - underscore_d
5个回答

96

TL;DR 不是所有情况下都是如此,通常异常处理在非异常路径上比错误码处理更快。


显然要看与什么相比较。与不处理错误相比,异常处理会降低性能;但是性能是否值得缺少正确性?我认为不值得,因此假设您所说的是与使用if语句检查的错误码进行比较。

在这种情况下,情况有所不同。有多种机制用于实现异常处理。事实上,它们可以通过一种与if语句非常相似的机制来实现,从而使它们具有相同的成本(或略高)。

但是,在C ++中,所有主要编译器(gcc在4.x系列中引入了它,MSVC用于64位代码)现在都使用零成本异常模型。如果您阅读Need4Sleep链接的技术论文,它被列为表驱动方法。这个想法是对于每个可能引发异常的程序点,您在侧表中注册一些位和片段,以便找到正确的catch子句。老实说,它在实现方面比旧策略更加复杂,但是零成本名称来源于它如果没有引发异常则是免费的。与CPU上的分支错误预测相比较。另一方面,当引发异常时,惩罚是巨大的,因为该表存储在冷区域(因此可能需要往返RAM甚至更糟)……但异常是例外情况,对吧?

总之,使用现代C ++编译器时,异常处理比错误码处理更快,但会增加二进制文件大小(由于静态表)。


为了全面起见,还有第三种选择:中止。与通过状态或异常传播错误不同,中止进程是可能的。但这只适用于有限数量的情况,然而它比前两种选择都要优化得多。


7
请查看draft Technical Report on C++ Performance的第5.4节,该节专门讨论C ++中try-catch语句的开销。
以下是该节的一小部分内容:
5.4.1.1.2“代码”方法的时间开销
On entry to each try-block
    ♦ Commit changes to variables enclosing the try-block
    ♦ Stack the execution context 
    ♦ Stack the associated catch clauses 
• On exit from each try-block
    ♦ Remove the associated catch clauses 
    ♦ Remove the stacked execution context 
• When calling regular functions 
    ♦ If a function has an exception-specification, register it for checking 
• As local and temporary objects are created 
    ♦ Register each one with the current exception context as it is created 
• On throw or re-throwLocate the corresponding catch clause (if any) – this involves some runtime check (possibly resembling RTTI checks) 
    If found, then: 
    destroy the registered local objects 
    check the exception-specifications of the functions called in-between 
    use the associated execution context of the catch clause 
    Otherwise: 
    call the terminate_handler6

6
总结一下,以便当链接不可避免地失效时,答案仍然有用。 - Patashu
1
@Patashu open-std 是一个持有网站,其主要目标是保留这些链接。 - Syntactic Fructose
4
即使是有良好意图的网站也有可能崩溃。Stack Overflow的政策是不允许仅包含链接的答案。 - Patashu
7
意图可能是好的,但文档明确列出了2种方法(代码驱动和表格驱动),而你不幸地列出了代码驱动的方法……目前(在主要编译器中)只有MSVC在32位模式下使用。相反,表驱动方法比“if”语句更快,并且更加臃肿(虽然臃肿的部分被隔离在冷代码区域)。我鼓励你阅读一下它,它的昵称是零成本异常模型。 - Matthieu M.

2

这要看情况。对于异常处理,编译器必须做一些事情,否则它无法执行堆栈展开等操作。也就是说,异常处理会降低性能,即使不抛出异常也是如此。这取决于您的编译器实现,降低程度不同。

另一方面,您必须自问:如果您手动插入错误处理代码,它真的会更快吗(请测量一下,不要瞎猜)?它能做到异常处理程序那样吗(客户端无法忽略异常,但可以忽略错误代码,并且异常可以执行堆栈展开,而错误码不能)?此外,使用异常处理可以编写更易于维护的代码。

简而言之,除非您的代码非常非常非常非常非常时间关键,否则使用异常处理。即使您最终决定不使用,也要先进行测量。有一个例外规则:在模块边界或析构函数内抛出异常是不明智的。


2

这要看具体的编译器。

如果编译器偏向于认为异常抛出是真正的异常情况,那么你可以实现一种方案,即在没有异常的情况下,你可以零开销地运行,但反过来,在发生异常和/或代码大小较大时,代价会更高。

要实现零开销的方法,你可以注意到,在C++中,你不能动态更改代码,因此一旦你知道堆栈帧和返回地址,就可以确定在取消堆栈展开或者存在异常处理代码段的情况下必须销毁哪些对象。因此,抛出异常的代码可以检查一个全局函数调用站点表,以决定应该执行什么操作。

另一方面,你可以通过在正常执行期间准备要明确销毁的对象列表和异常处理代码的地址来加快异常抛出速度。这将使正常代码变慢,但异常处理变得更快,而且我认为代码会稍微变小一些。

不幸的是,在C++中,没有标准的方式可以完全放弃异常支持,因此必须为这种可能性付出代价:标准库会抛出异常,并且任何调用未知代码的代码(例如使用函数指针或调用虚方法)都必须准备处理异常。


1
我建议在进行内存分配、删除、调用其他复杂函数等操作的函数中添加try catch。实际上,就性能而言,try catch会增加一点开销。
但是考虑到捕获未知异常的优点,它非常有帮助。良好的编程实践总是建议在代码中添加某种异常处理,除非你是一个例外的程序员。
我想知道为什么您如此关心小的性能问题,而不是整个程序被异常卡住。

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