潜在的异常会带来开销吗?
当未引发异常时,与不引发异常的类似代码相比,一段可能引发异常的代码的性能是否会下降?
Will a piece of code that potentially throws an exception have a degraded performance compared a similar code that doesn't, when the exception isn't thrown?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
已经证明,可以在“正常”(与异常无关)代码中实现零开销的 C++ 异常处理机制。然而,在实践中,编译器通常坚持使用更简单的实现,这通常会导致“正常”代码效率较低。编译器必须考虑到函数层次结构中潜在异常的可能性,因此生成一些额外的家庭操作,以便在抛出异常时能够正确展开堆栈。无论是否抛出异常,这些额外的家庭代码都会影响代码的整体效率。
这都是 QoI(实施质量)问题。它是编译器特定的。检查您的编译器以获取更多详细信息。一些编译器实际上提供了启用/禁用 C++ 异常的选项,以便在根本不使用异常时生成最有效的代码。
It has been demonstrated that it is possible to implement C++ exception handling mechanism with zero overhead in "normal" (not exception-related) code. However, in practice compilers usually stick to simpler implementations, which usually result in less efficient "normal" code. Compilers have to account for the possibility of a potential exception flying through the function hierarchy and therefore generate some additional household operations to enable proper stack unwinding if an exception is thrown. This extra household code affects the overall efficiency of the code regardless of whether an exception is ever thrown or not.
This is all a QoI (quality-of-implementation) issue. It is compiler specific. Check your compiler for more details. Some compilers actually offer and option to enable/disable C++ exceptions in order to make it possible to generate the most efficient code when exceptions are not used at all.
这取决于;基于表的实现(我相信现代 g++ 使用它,也是 Windows 中 x64 二进制文件使用的策略)对于非抛出异常的处理开销为零(以稍微增加内存使用为代价)。基于函数的异常处理(x86 Windows 使用的)即使对于未引发的异常也会造成较小的性能影响。
It depends; table-based implementations (which I believe modern g++ uses, and which is the strategy used for x64 binaries in Windows) are zero processing overhead for non-thrown exceptions (at the expense of marginally more memory usage). Function-based exception handling (which x86 Windows uses) incurs a small performance hit even for non-thrown exceptions.
尝试是便宜的,捕获是便宜的,扔是昂贵的。显然,尝试中包含了一些额外的处理执行代码。
对特殊的东西使用异常 - 那么开销就不重要了。
Try is cheap, catch is cheap, throw is expensive. There's obviously a little extra processing executing code that is wrapped inside a try.
Use exception for exceptional stuff - then the overhead won't matter.
这取决于您的编译器。一些编译器/运行时组合在进入带有 catch 处理程序的块时做了额外的工作。其他人构建静态数据结构,所有工作都在抛出时发生。在所有情况下,进入成本都会低于 throw,但您要谨慎对待内部循环中的 catch 块。衡量您关心的编译器的时间成本。
This depends on your compiler. Some compiler/runtime combinations do extra work on entry to a block with catch handlers. Others build a static data structure and all the work happens at throw. The entry cost will be lower than throw in all cases, but you want to be cautious about catch block in inner loops. Measure the time cost with the compiler you care about.
这取决于编译器,但答案几乎肯定是“是”。具体来说,如果作用域包含具有重要析构函数的对象,则该对象需要向运行时注册,以便在异常时调用析构函数。例如:
除了构造和处理一百万个事物之外,这还将生成一百万个函数调用来注册和取消注册每个事物,以防对 Process 的调用抛出异常。
除此之外,进入或离开
try
块时会产生少量开销,因为相应的catch
块会添加到异常处理程序堆栈中或从异常处理程序堆栈中删除。It depends on the compiler, but the answer is almost certainly "yes". Specifically, if a scope contains an object with a non-trivial destructor, then that object will need to be registered with the runtime in order to call the destructor on an exception. For example:
In addition to constructing and processing a million Things, this will also generate a million function calls to register and unregister each Thing in case the call to Process throws.
On top of this, there is a small overhead when entering or leaving
try
blocks, as the correspondingcatch
block is added to or removed from the stack of exception handlers.由于编译器需要生成在引发异常时进入堆栈的代码,因此在幕后添加了一些代码。但如果它多得多,那就有争议了:
当变量超出范围时自动调用析构函数生成的代码,
以及您必须编写的代码来检查每个调用的退出状态并处理错误。
昂贵的是捕获错误:try ... catch 语句以及抛出和捕获异常时会发生什么:
保留有关添加 try ... catch 的每个位置的信息(也隐式添加,例如在析构函数周围或在异常规范处) ),
要展开的堆栈(以及要调用的析构函数)以查找看起来的内容像简单跳转一样,
匹配抛出到 catch() 子句的异常,
复制异常。
Since compiler needs to generate code that will inwind stack when exception is thrown, there is some added code behind the scenes. But it's debatable if it's considerably more, then:
code that is generated to automatically call destructors when variables go out of scope,
and code you would have to write to check exit status of every call and handle error.
What is expensive is catching errors: try ... catch statements and what happens when exception is thrown and caught:
keeping information about each place where try ... catch is added (also implicitly added e.g. around destructors or at exception specifications),
lot's of stack to unwind (and destructors to call) for something that looks like simple jump,
matching exception thrown to catch() clauses,
copying exceptions.
与没有异常处理的代码相比,带有异常处理的代码速度更慢,也更大。
因为当引发异常时,它必须在堆栈展开过程中对要销毁的对象进行簿记。
Ya code with exception handling is slower and also larger as compared to code without exception handing.
Since it has to do bookkeeping for the objects to be destructed during stack unwinding process when exception is raised.
不管它是否“在不抛出异常时可以实现零开销”以及所有这些理论讨论,现实情况是,对于某些编译器(g++ 4.4),即使使用 -O2 优化,也只是在一个内部有一个 throw 子句这一事实紧循环函数(即cpu-bound)将使函数速度变慢 10 到 100 倍,这里有一个问题:此时 throw 实际上从未被执行。< /strong>
所以我的建议是避免像瘟疫一样在 C++ 中进行标准异常处理(除非你证明我错了);如果您想对性能敏感的应用程序进行错误处理,请使用 boost.context
regardless of if its "possible to implement zero overhead when no exceptions are thrown" and all that theoretical discussion, the reality is that for certain compilers (g++ 4.4) even with -O2 optimizations, just the fact that you have a throw clause inside a tight-looped function (that is cpu-bound) will make the function somewhere between 10 and 100x times slower, and here is the catch: this is when the throw is actually never executed.
So my suggestion is to avoid standard exception handling in c++ like the plague (unless you prove me wrong); use boost.context if you want to make error handling on a performance-sensitive application
C++ 性能技术报告草案的第 5.4 节是完全致力于异常的开销。
Section 5.4 of the draft Technical Report on C++ Performance is entirely devoted to the overhead of exceptions.