了解C++异常处理的系统开支

了解C++异常处理的系统开支,第1张

了解C++异常处理的系统开支,第2张

为了在运行时处理异常,程序必须记录大量信息。无论在哪里执行,程序必须能够识别如果在这里抛出异常,将释放哪个对象;为了从try块中退出,程序必须知道每个入口点;对于每个try块,它们必须跟踪其相关的catch子句以及这些catch子句可以捕获的异常类型。这种信息的记录不是没有成本的。不需要运行时比较来确保程序符合异常规范,当抛出异常时,释放相关对象和匹配正确的catch语句也没有额外的开销。但是异常处理是有代价的。即使不使用try、throw或catch关键字,仍然要付出一些代价。

让我们从不使用任何异常处理特性所要付出的代价开始。你需要在空之间建立数据结构来跟踪对象是否构造完整(见第10条),你还需要系统时间来保持这些数据结构不断更新。这些开销一般不会很大,但当不支持异常的方法编译的程序运行速度一般比支持异常的程序快时,所占的空就更少了。

理论上,你不能这样选择:C++编译器必须支持异常,也就是说,你不能在不使用异常处理的情况下,要求编译器厂商消除这种开销,因为程序一般是由多个独立生成的目标文件组成的。一个目标文件不处理异常并不意味着其他目标文件不处理异常。即使组成可执行文件的目标文件没有被异常处理,那么它们所连接的库呢?如果程序的任何部分使用异常,其他部分也必须支持异常。否则,程序不可能在运行时提供正确的异常处理。

然而,这只是一种理论。事实上,大多数支持异常的编译器制造商允许您自由控制是否在生成的代码中包含支持异常的内容。如果你知道你的程序的任何部分不使用try、throw或catch,并且你也知道连接的库不使用try、throw或catch,你可以通过不支持异常处理的方法来编译,这可以减小程序的大小和速度,或者你将不得不为一个不必要的功能付出代价。随着时间的推移,使用带有异常处理的库是常见的,上述方法会逐渐变得不可用。但是根据目前软件开发的情况,如果你已经决定不使用任何异常特性,那么用不支持异常的方法来编译程序是一种合理的方法。对于希望避免异常的库来说,这也是一种性能优化的好方法,可以确保异常不会从客户端程序传递到库。但是,这也会阻止客户端程序重新定义库中声明的虚函数,并且不允许在客户端定义回调函数。

使用异常处理的第二个开销来自try块。无论你什么时候使用它,也就是无论你什么时候想捕捉异常,你都要为此付出代价。不同的编译器以不同的方式实现try块,因此编译器之间的开销是不同的。粗略估算,如果使用try块,代码的大小会增加5%-10%,运行速度也会同比例变慢。这仍然假设程序不会抛出异常。我这里讨论的只是程序中使用try块的开销。为了减少开销,应该避免使用无用的try块。

编译器为异常规范生成的代码与为try块生成的代码一样多,因此异常规范通常会消耗与tyr块一样多的系统开销。什么?说你认为异常的规格说明只是一个规格说明,你认为它们不会生成代码?好了,现在你应该对此有了新的认识。

现在我们来看问题的核心,看看抛出异常的开销。其实这个问题我们不用太在意,因为异常是很少发生的,这类事件的发生往往被描述为例外(异常,罕见)。80-20法则(见第16条)告诉我们,这样的事件不会对整个程序的性能产生太大的影响。但是我知道你仍然很好奇如果抛出一个异常要花多少钱。答案是可能比较大。与正常的函数返回相比,通过抛出异常从函数返回可能要慢三个数量级。这很贵。但是只有抛出异常才会有这个开销,一般不会发生。但是如果用一个异常来表示一个常见的情况,比如遍历数据结构或者结束一个循环,就必须重新考虑了。

但是请等一下。我是怎么知道这些事情的?如果异常支持是大部分编译器的新特性,不同的编译器异常方法不一样,我怎么能说程序的规模会增加5%-10%,其速度会同比例变慢,而如果抛出大量异常,程序的运行速度会变慢一个数量级呢?答案是可怕的:一些谣言和一些基准(见第23条)。事实是,大多数人,包括编译器制造商,在异常处理方面几乎没有经验,所以尽管我们知道异常确实会带来开销,但很难预测开销的确切数量。

谨慎的做法是对该条款描述的费用有所了解,但不要深究具体数量。无论异常处理的开销有多大,我们都必须坚持只在必要时支付的原则。为了最小化您的异常开销,尽可能使用不支持异常的方法来编译程序,将try块和异常规范的使用限制在您真正需要它们的地方,并且只在它们异常时抛出异常。如果您仍然有性能问题,请从整体上评估您的软件,以确定异常支持是否是一个促成因素。如果是,可以考虑选择其他编译器,在C++异常处理上可以达到更高的效率。

位律师回复
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » 了解C++异常处理的系统开支

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情