2

当不引发异常时,与不会引发异常的类似代码相比,一段可能引发异常的代码是否会降低性能?

4

9 回答 9

4

已经证明可以在“正常”(非异常相关)代码中以零开销实现 C++ 异常处理机制。然而,实际上编译器通常坚持更简单的实现,这通常会导致效率较低的“正常”代码。编译器必须考虑到潜在异常通过函数层次结构的可能性,因此生成一些额外的家庭操作以在抛出异常时启用正确的堆栈展开。无论是否抛出异常,这些额外的家庭代码都会影响代码的整体效率。

这都是一个 QoI(实施质量)问题。它是特定于编译器的。检查您的编译器以获取更多详细信息。一些编译器实际上提供了启用/禁用 C++ 异常的选项,以便在根本不使用异常时生成最有效的代码。

于 2009-12-02T17:31:31.137 回答
3

这取决于; 基于表的实现(我相信现代 g++ 使用它,这是 Windows 中 x64 二进制文件使用的策略)对于非抛出异常来说是零处理开销(以稍微多一点的内存使用为代价)。基于函数的异常处理(x86 Windows 使用)即使对于未抛出的异常也会造成很小的性能损失。

于 2009-12-02T18:30:16.650 回答
1

这取决于您的编译器。一些编译器/运行时组合在进入带有 catch 处理程序的块时会做额外的工作。其他人构建静态数据结构,所有工作都发生在 throw 上。在所有情况下,入口成本都会低于 throw,但您要小心内部循环中的 catch 块。使用您关心的编译器衡量时间成本。

于 2009-12-02T17:21:07.793 回答
1

这取决于编译器,但答案几乎肯定是“是”。具体来说,如果作用域包含一个具有非平凡析构函数的对象,那么该对象将需要向运行时注册,以便在异常时调用析构函数。例如:

struct Thing
{
    ~Thing();
    void Process();
};

for (int i = 0; i < 1000000; ++i)
{
    Thing thing;
    thing.Process();
}

除了构造和处理一百万个事物之外,这还将生成一百万个函数调用来注册和注销每个事物,以防对 Process 的调用抛出。

最重要的是,进入或离开块时会有一点开销try,因为相应的catch块被添加到异常处理程序堆栈中或从堆栈中删除。

于 2009-12-02T17:24:44.007 回答
1

由于编译器需要生成在抛出异常时将内卷堆栈的代码,因此在幕后添加了一些代码。但是,如果它更多,那就值得商榷了:

  • 当变量超出范围时自动调用析构函数的代码,

  • 以及您必须编写的代码来检查每个调用的退出状态并处理错误。

昂贵的是捕获错误:try ... catch 语句以及抛出和捕获异常时会发生什么:

  • 保留有关添加 try ... catch 的每个位置的信息(也隐式添加,例如围绕析构函数或异常规范),

  • 很多堆栈要展开(和析构函数调用)看起来像简单的跳转,

  • 匹配的异常抛出到 catch() 子句,

  • 复制异常。

于 2009-12-02T17:37:14.647 回答
1

尝试很便宜,抓很便宜,扔很贵。显然有一些额外的处理执行代码包含在 try 中。

对特殊的东西使用异常——那么开销就无关紧要了。

于 2009-12-02T17:17:12.683 回答
0

与没有异常处理的代码相比,具有异常处理的代码更慢且更大。

因为当引发异常时,它必须在堆栈展开过程中对要销毁的对象进行簿记。

于 2009-12-02T17:25:19.487 回答
0

不管它是否“在没有抛出异常时可能实现零开销”以及所有的理论讨论,现实情况是对于某些编译器(g++ 4.4)即使使用 -O2 优化,只是你在 a 中有一个 throw 子句的事实紧密循环的函数(即cpu-bound)将使该函数慢 10 到 100 倍之间,这就是问题所在:这是实际上从不执行 throw 的时候。

所以我的建议是避免像瘟疫一样在 C++ 中进行标准异常处理(除非你证明我错了);如果您想对性能敏感的应用程序进行错误处理,请使用 boost.context

于 2010-11-24T21:19:04.923 回答
0

C++ 性能技术报告草案的第 5.4 节完全致力于异常的开销。

于 2010-12-15T15:41:32.493 回答