在实时环境中异常仍然是不可取的吗?
几年前,我被告知,在嵌入式系统或(非 Linux)内核开发等实时应用程序中,C++ 异常是不可取的。 (也许这个教训是来自 gcc-2.95 之前的)。但我也知道,异常处理已经变得更好了。
那么,在实践中,实时应用程序上下文中的 C++-Exception 是
- 完全不需要的吗?
- 甚至可以通过编译器开关关闭?
- 或者非常小心地使用?
- 或者现在处理得很好,以至于人们可以几乎自由地使用它们,但要记住几件事?
- C++11 对此有何改变吗?
更新:异常处理是否真的需要启用RTTI(正如一位回答者建议的那样)?是否涉及动态转换或类似内容?
A couple of years ago I was taught, that in real-time applications such as Embedded Systems or (Non-Linux-)Kernel-development C++-Exceptions are undesirable. (Maybe that lesson was from before gcc-2.95). But I also know, that Exception Handling has become better.
So, are C++-Exceptions in the context of real-time applications in practice
- totally unwanted?
- even to be switched off via via compiler-switch?
- or very carefully usable?
- or handled so well now, that one can use them almost freely, with a couple of things in mind?
- Does C++11 change anything w.r.t. this?
Update: Does exception handling really require RTTI to be enabled (as one answerer suggested)? Are there dynamic casts involved, or similar?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
异常现在得到了很好的处理,并且用于实现它们的策略实际上使它们比测试返回代码更快,因为只要您不抛出任何异常,它们的成本(就速度而言)几乎为零。
然而它们确实有成本:代码大小。异常通常与 RTTI 齐头并进,不幸的是 RTTI 与任何其他 C++ 功能不同,因为您可以为整个项目激活或停用它,一旦激活,它将为任何碰巧具有虚拟方法的类生成补充代码,从而违背了“你不为不使用的东西付费”的心态。
此外,它确实需要补充代码来进行处理。
因此,异常的成本不应以速度来衡量,而应以代码增长来衡量。
编辑:
来自
@Space_C0wb0y
:此博客文章提供了一个简短的概述,并介绍了两种常见的实现异常跳转和零成本的方法。顾名思义,好的编译器现在使用零成本机制。维基百科有关异常处理的文章讨论了所使用的两种机制。 零成本机制是表驱动机制。
编辑:
来自
@Vlad Lazarenko
,我在上面引用了他的博客,抛出异常的存在可能会阻止编译器内联和优化寄存器中的代码。Exceptions are now well-handled, and the strategies used to implement them make them in fact faster than testing return code, because their cost (in terms of speed) is virtually null, as long as you do not throw any.
However they do cost: in code-size. Exceptions usually work hand in hand with RTTI, and unfortunately RTTI is unlike any other C++ feature, in that you either activate or deactivate it for the whole project, and once activated it will generated supplementary code for any class that happens to have a virtual method, thus defying the "you don't pay for what you don't use mindset".
Also, it does require supplementary code for its handling.
Therefore the cost of exceptions should be measured not in terms of speed, but in terms of code growth.
EDIT:
From
@Space_C0wb0y
: This blog article gives a small overview, and introduces two widespread methods for implementing exceptions Jumps and Zero-Cost. As the name implies, good compilers now use the Zero-Cost mechanism.The Wikipedia article on Exception Handling talk about the two mechanisms used. The Zero-Cost mechanism is the Table-Driven one.
EDIT:
From
@Vlad Lazarenko
whose blog I had referenced above, the presence of exception thrown might prevent a compiler from inlining and optimizing code in registers.仅回答更新:
异常处理实际上在某一方面需要比RTTI 和动态转换更强大的东西。考虑以下代码:
因此,当另一个 TU 中的函数抛出异常时,它将查找堆栈(要么立即检查所有级别,要么在堆栈展开期间一次检查一个级别,这取决于实现)是否有 catch与被抛出的对象匹配的子句。
要执行此匹配,可能不需要 RTTI 方面在每个对象中存储类型,因为抛出的异常的类型是抛出表达式的静态类型。但它确实需要以
instanceof
的方式比较类型,并且需要在运行时执行此操作,因为some_function_in_another_TU
可以从任何地方调用,并且可以使用任何类型的 catch 来调用。堆。与dynamic_cast
不同,它需要对没有虚拟成员函数的类型以及非类类型的类型执行运行时instanceof检查。最后一部分不会增加难度,因为非类类型没有层次结构,因此所需的只是类型相等,但您仍然需要可以在运行时进行比较的类型标识符。因此,如果启用异常,则需要 RTTI 中执行类型比较的部分,例如
dynamic_cast
的类型比较,但涵盖更多类型。您不一定需要 RTTI 的一部分来存储用于在每个类的 vtable 中执行此比较的数据,可以从对象访问该数据 - 数据只能在每个 throw 表达式和每个 catch 子句的点进行编码。但我怀疑这是否会节省大量成本,因为typeid
对象并不是很大,它们包含符号表中经常需要的名称,以及一些用于描述类型层次结构的实现定义的数据。因此,到那时您可能还拥有所有 RTTI。Answer just to the update:
Exception-handling actually requires something more powerful than RTTI and dynamic cast in one respect. Consider the following code:
So, when the function in the other TU throws, it's going to look up the stack (either check all levels immediately, or check one level at a time during stack unwinding, that's up to the implementation) for a catch clause that matches the object being thrown.
To perform this match, it might not need the aspect of RTTI that stores the type in each object, since the type of a thrown exception is the static type of the throw expression. But it does need to compare types in an
instanceof
way, and it needs to do this at runtime, becausesome_function_in_another_TU
could be called from anywhere, with any type of catch on the stack. Unlikedynamic_cast
, it needs to perform this runtime instanceof check on types which have no virtual member functions, and for that matter types which are not class types. That last part doesn't add difficulty, because non-class types have no hierarchy, and so all that's needed is type equality, but you still need type identifiers that can be compared at runtime.So, if you enable exceptions then you need the part of RTTI that does type comparisons, like
dynamic_cast
's type comparisons but covering more types. You don't necessarily need the part of RTTI that stores the data used to perform this comparison in each class's vtable, where it's reachable from the object -- the data could instead only be encoded at the point of each throw expression and each catch clause. But I doubt that's a significant saving, sincetypeid
objects aren't exactly massive, they contain a name that's often needed anyway in a symbol table, plus some implementation-defined data to describe the type hierarchy. So probably you might as well have all of RTTI by that point.异常的问题不一定是速度(根据实现的不同,速度可能会有很大差异),而是它们实际所做的事情。
在实时世界中,当您对操作有时间限制时,您需要确切地知道代码的作用。异常提供了可能影响代码整体运行时间的快捷方式(例如,异常处理程序可能不适合实时约束,或者由于异常,您可能根本不返回查询响应)。
如果您的意思是“实时”实际上是“嵌入式”,那么如上所述,代码大小就成为一个问题。嵌入式代码不一定是实时的,但它可能有大小限制(而且经常如此)。
此外,嵌入式系统通常设计为在无限事件循环中永远运行。异常可能会将您带出该循环的某个位置,并且还会损坏您的内存和数据(因为堆栈展开) - 再次取决于您对它们的处理方式以及编译器如何实际实现它。
因此,安全总比后悔好:不要使用异常。如果您可以承受偶尔的系统故障,如果您在一个单独的任务中运行,那么可以轻松地重新启动,如果您不是真正的实时,只是假装是实时的 - 那么您可能可以尝试一下。如果您正在为心脏起搏器编写软件 - 我更愿意检查返回代码。
The problem with exceptions is not necessarily the speed (which may differ greatly, depending on the implementation), but it's what they actually do.
In the real-time world, when you have a time constraint on an operation, you need to know exactly what your code does. Exceptions provide shortcuts that may influence the overall run time of your code (exception handler may not fit into the real-time constraint, or due to an exception you might not return the query response at all, for example).
If you mean "real-time" as in fact "embedded", then the code size, as mentioned, becomes an issue. Embedded code may not necessarily be real-time, but it can have size constraint (and often does).
Also, embedded systems are often designed to run forever, in an infinite event loop. Exception may take you somewhere out of that loop, and also corrupt your memory and data (because of the stack unwinding) - again, depends on what you do with them, and how the compiler actually implements it.
So better safe than sorry: don't use exceptions. If you can sustain occasional system failures, if you're running in a separate task than can be easily restarted, if you're not really real-time, just pretend to be - then you probably can give it a try. If you're writing software for a heart-pacer - I would prefer to check return codes.
C++ 异常仍然不受每个实时环境的支持,无法在任何地方都可以接受。
在视频游戏的特定示例中(每帧的软截止时间为 16.6 毫秒),领先的编译器以这样的方式实现 C++ 异常:只需在程序中打开异常处理就会显着减慢速度并增加代码大小,无论如何你是否真的抛出异常。鉴于性能和内存对游戏机都至关重要,这就是一个大问题:例如,PS3 的 SPU 单元有 256kb 的内存用于存储代码和数据!
最重要的是,抛出异常仍然相当慢(如果您不相信我,请测量一下)并且可能导致堆释放,这在您没有微秒空闲的情况下也是不希望的。
我见过的这个规则的唯一一个……呃……例外是每次应用程序运行时可能会抛出一次异常 - 不是每帧一次,而是字面上一次强>。在这种情况下,结构化异常处理是一种可接受的方法,可以在游戏崩溃时从操作系统捕获稳定性数据并将其转发回开发人员。
C++ exceptions still aren't supported by every realtime environment in a way that makes them acceptable everywhere.
In the particular example of video games (which have a soft 16.6ms deadline for every frame), the leading compilers implement C++ exceptions in such a way that simply turning on exception handling in your program will significantly slow it down and increase code size, regardless of whether you actually throw exceptions or not. Given that both performance and memory are critical on a game console, that's a dealbreaker: the PS3's SPU units, for example, have 256kb of memory for both code and data!
On top of this, throwing exceptions is still quite slow (measure it if you don't believe me) and can cause heap deallocations which are also undesirable in cases where you haven't got microseconds to spare.
The one... er... exception I have seen to this rule is cases where the exception might get thrown once per app run -- not once per frame, but literally once. In that case, structured exception handling is an acceptable way to catch stability data from the OS when a game crashes and relay it back to the developer.
当抛出异常时,异常机制的实现通常非常慢,否则使用它们的成本几乎为零。在我看来,如果正确使用异常,它们将非常有用。
在 RT 应用程序中,仅当出现问题且程序必须停止并修复问题(并且可能需要等待用户交互)时才应引发异常。在这种情况下,解决问题需要更长的时间。
异常提供了报告错误的隐藏路径。它们使代码更短、更具可读性,因此更容易维护。
The implementation of the exception mechanism is usually very slow when an exception is thrown, otherwise the costs of using them is almost none. In my opinion exceptions are very useful if you use them correctly.
In RT applications, exceptions should be thrown only when something goes bad and the program has to stop and fix the issue (and possible wait for the user interaction). Under such circumstances, it takes longer to fix the issue.
Exceptions provide hidden path of reporting an error. They make the code more shorter and more readable, therefore easier maintenance.
C++ 异常处理的典型实现仍然不理想,并且可能导致整个语言实现对于某些资源极其有限的嵌入式目标几乎无法使用,即使用户代码没有显式使用这些功能也是如此。最近的 WG21 论文将这称为“零开销原则违反”,请参阅 N4049 和 N4234了解详情。在这种环境中,无论应用程序是否实时,异常处理都不会按预期工作(消耗合理的系统资源)。
然而,嵌入式环境中应该有能够承受这些开销的实时应用程序,例如手持设备中的视频播放器。
应始终谨慎使用异常处理。在任何平台(不仅针对嵌入式环境)的实时应用程序中每帧抛出和捕获异常都是糟糕的设计/实现,通常是不可接受的。
Typical implementations of C++ exception handling were still not ideal, and might cause the entire language implementation almost unusable for some embedded targets with extremely limited resources, even if the user code is not explicitly using these features. This is referred as "zero overhead principle violation" by recent WG21 papers, see N4049 and N4234 for details. In such environments, exception handling does not work as expected (consuming reasonable system resources) whether the application is real-time or not.
However, there should be real-time applications in embedded environments which can afford these overhead, e.g. a video player in a handheld device.
Exception handling should always be used carefully. Throwing and catching exceptions per frame in a real-time application for any platforms (not only for embedded environments) is a bad design/implementation and not acceptable in general.
嵌入式/实时开发中通常存在 3 或 4 个约束 - 特别是当这意味着
代码质量 - C++ 代码通常会在看起来微不足道的语句中隐藏大量复杂性,从而使代码难以直观地审核错误。异常将处理与位置分离,使得证明测试的代码覆盖率变得困难。
C++ 公开了一个非常简单的内存模型:new 从无限的空闲存储中分配,直到用完为止,并抛出异常。在内存受限的设备中,可以编写更有效的代码来显式使用固定大小的内存块。 C+ 对几乎所有操作的隐式分配使得审计内存使用变得不可能。此外,大多数 C++ 堆都表现出令人不安的特性,即内存分配所需的时间没有可计算的上限 - 这再次使得在需要固定上限的实时设备上证明算法的响应时间变得困难。
There are generally 3 or 4 constraints in embedded / realtime development - especially when that implies kernel mode development
at various points - usually while handling hardware exceptions - operations MUST NOT throw more hardware exceptions. c++'s implicit data structures (vtables) and code (default constructors & operators & other implicitly generated code to support the c++ exception mechanisim) are not placeable, and cannot as a result be guaranteed to be placed in non paged memory when executed in this context.
Code quality - c++ code in general can hide a lot of complexity in statements that look trivial making code difficult to visually audit for errors. exceptions decouple handling from location, making proving code coverage of tests difficult.
C++ exposes a very simple memory model: new allocates from an infinite free store, until you run out, and it throws an exception. In memory constrained devices, more efficient code can be written that makes explicit use of fixed size blocks of memory. C+'s implicit allocations on almost any operation make it impossible to audit memory use. Also, most c++ heaps exhibit the disturbing property that there is no computable upper limit on how long a memory allocation can take - which again makes it difficult to prove the response time of algorithms on realtime devices where fixed upper limits are desirable.