Are Exceptions in C++ really slow

我在看 C + + 中的系统错误处理ーー Andrei Alexandrescu的时候,他声称 C + + 中的异常非常非常慢。

C + + 98还是这样吗?

55513 次浏览

Like in silico said its implementation dependent, but in general exceptions are considered slow for any implementation and shouldn't be used in performance intensive code.

编辑: 我并不是说完全不要使用它们,但是对于性能密集型代码,最好避免使用它们。

这取决于编译器。

例如,众所周知,GCC 在处理异常时的性能非常差,但是在过去几年中这种情况有了很大改善。

But note that handling exceptions should - as the name says - be the exception rather than the rule in your software design. When you have an application which throws so many exceptions per second that it impacts performance and this is still considered normal operation, then you should rather think about doing things differently.

异常是一种很好的方法,通过消除所有笨重的错误处理代码,使代码更具可读性,但一旦它们成为正常程序流的一部分,它们就变得非常难以遵循。记住,一个 throw几乎是一个 goto catch的伪装。

今天用于异常的主要模型(Itanium ABI,VC + + 64 bit)是零成本模型异常。

其思想是,编译器不会通过设置一个约束和显式地检查到处是否存在异常来浪费时间,而是生成一个侧表,将可能抛出异常的任何点(Program Counter)映射到处理程序列表。当抛出异常时,将参考此列表来选择正确的处理程序(如果有的话) ,并取消堆栈。

与典型的 if (error)战略相比:

  • the Zero-Cost model, as the name implies, is free when no exceptions occur
  • 当异常发生时,它的成本大约是 if的10倍/20倍

然而,衡量成本并非微不足道:

  • 副表通常是 cold,因此从内存中提取它需要很长时间
  • 确定正确的处理程序涉及到 RTTI: 许多需要获取的 RTTI 描述符,散布在内存中的 RTTI 描述符,以及需要运行的复杂操作(基本上是每个处理程序的 dynamic_cast测试)

因此,大部分缓存都丢失了,因此与纯 CPU 代码相比并不是微不足道的。

Note: for more details, read the TR18015报告书第5.4章例外情况处理(pdf)

因此,是的,异常是缓慢的 在特殊的道路上,但是它们通常比显式检查(if策略)更快。

Note: Andrei Alexandrescu seems to question this "quicker". I personally have seen things swing both ways, some programs being faster with exceptions and others being faster with branches, so there indeed seems to be a loss of optimizability in certain conditions.


这重要吗?

我会说没有。编写程序时应该考虑到 可读性,而不是性能(至少不能作为第一个标准)。当预期调用方不能或不希望当场处理故障并将其传递给堆栈时,可以使用异常。额外的好处: 在 C + + 中,11个异常可以使用标准库在线程之间进行封送处理。

这很微妙,我认为 map::find不应该抛出,但是我可以接受 map::find返回一个 checked_ptr,如果取消引用尝试失败,它会抛出,因为它是 null: 在后一种情况下,就像 Alexandresu 引入的类一样,打电话的人选择在显式检查和依赖异常之间。授权给打电话的人而不给他更多的责任通常是一个好的设计的标志。

当这个问题被贴出来的时候,我正在去看医生的路上,一辆出租车在等着我,所以我只有时间做一个简短的评论。但是现在我已经发表了评论,投了赞成票和反对票,我最好加上我自己的回答。即使 马修的回答已经很好了。


与其他语言相比,C + + 中的异常是否特别慢?

重申一下

我在看 C + + 中的系统错误处理ーー Andrei Alexandrescu的时候,他说 C + + 中的例外是非常非常慢的

如果这就是安德烈所宣称的,那么这一次他即使不是彻头彻尾的错误,也是非常具有误导性的。对于引发/抛出的异常,与语言中的其他基本操作相比,不管编程语言是什么总是比较慢。不仅在 C + + 中如此,而且在 C + + 中比在其他语言中更是如此,正如声称所指出的那样。

一般来说,无论语言如何,这两个基本的语言特性的数量级比其他的慢,因为它们转换成处理复杂数据结构的例程调用

  • 抛出异常,然后

  • 动态内存分配动态内存分配。

令人高兴的是,在 C + + 中,通常可以同时避免这两种情况。

不幸的是 天下没有免费的午餐,即使 C + + 的默认效率非常接近。因为避免异常抛出和动态内存分配所获得的效率通常是通过在较低的抽象级别进行编码来实现的,使用 C + + 作为一种更好的 C & rdquo;。较低的抽象意味着更大的复杂性。

更大的复杂性意味着更多的时间花在维护上,代码重用带来的好处很少甚至没有,这是真正的金钱成本,即使很难估计或度量。例如,使用 C + + ,如果需要的话,可以用一些程序员的效率来交换执行效率。是否这样做在很大程度上是一个工程和直觉的决定,因为在实践中只有收益,而不是成本,可以很容易地估计和衡量。


对于 C + + 异常抛出性能有什么客观的衡量标准吗?

是的,国际 C + + 标准化委员会已经发布了 C + + 性能技术报告,TR18015


例外是缓慢的,这是什么 刻薄

这主要意味着一个 throw可以采取一个非常长的时间和交易; 相比,例如 int分配,由于搜索处理程序。

正如 TR18015在其第5.4节中讨论的,有两个主要的例外处理实施策略,

  • 每个 try块动态设置异常捕获的方法,以便在抛出异常时执行对处理程序动态链的搜索

  • 编译器生成静态查找表的方法,这些表用于确定引发异常的处理程序。

第一种非常灵活和通用的方法在32位 Windows 中几乎是强制性的,而在64位和 * nix-land 中,第二种更有效的方法是常用的。

此外,正如该报告所讨论的,对于每种方法,异常处理对效率的影响主要有三个方面:

  • try-街区,

  • 常规函数(优化机会) ,以及

  • throw-表达式。

主要是动态处理程序方法(32位 Windows)异常处理对 try块有影响,大多数情况下不考虑语言(因为这是由 Windows 的 结构化异常处理方案强制的) ,而静态表方法对 try块的成本大致为零。讨论这个问题将需要更多的空间和研究,而这对于一个“ SO”的答案来说是不切实际的。详情请看报告。

不幸的是,从2006年开始的这份报告,到2012年底已经有点过时了,据我所知,没有比这更新的报告了。

Another important perspective is that the impact of 例外情况的使用 on performance is very different from the isolated efficiency of the supporting language features, because, as the report notes,

当考虑异常处理时,它必须与其他方法进行对比 处理错误

例如:

  • 由于不同的编程风格(正确性)导致的维护成本

  • 冗余呼叫站点 if故障检查与集中式 try

  • 缓存问题(例如,缓存中可能适合较短的代码)

The report has a different list of aspects to consider, but anyway the only practical way to obtain hard facts about the execution efficiency is probably to implement the same program using exception and not using exceptions, within a decided cap on development time, and with developers familiar with each way, and then 量度.


什么是避免异常开销的好方法?

Correctness almost always trumps efficiency.

毫无例外,下列情况很容易发生:

  1. 一些代码 P 意味着获取一个资源或计算一些信息。

  2. 调用代码 C 应该检查成功/失败,但是没有。

  3. 在 C 后面的代码中使用不存在的资源或无效信息,造成一般混乱。

主要的问题是点(2) ,其中通常的 返回码方案不强制调用代码 C 进行检查。

确实有两种主要方法强制进行这种检查:

  • 当异常失败时,P 直接引发异常。

  • Where P returns an object that C has to 视察 before using its main value (otherwise an exception or termination).

第二种方法是 AFAIK,巴顿和纳克曼在他们的书 * 科学与工程 C + + : 高级技术导论与实例中首次描述了这种情况,他们在书中引入了一个名为 Fallow的类,用于 a & ldquo; may & rdquo; function result。Boost 库现在提供了一个类似的类 optional。而且您可以很容易地自己实现一个 Optional类,使用 std::vector作为非 POD 结果的值载体。

对于第一种方法,调用代码 C 别无选择,只能使用异常处理技术。然而,对于第二种方法,调用代码 C 自己可以决定是执行基于 if的检查,还是执行一般的异常处理。因此,第二种方法支持在程序员与执行时间之间进行效率权衡。


各种 C + + 标准对异常性能的影响是什么?

“I want to know is this still true for C++98”

C + + 98是第一个 C + + 标准。对于异常,它引入了异常类的标准层次结构(不幸的是并不完美)。对性能的主要影响是 异常规范(在 C + + 11中删除)的可能性,然而这种可能性从未被主 Windows C + + 编译器完全实现 Visual C + + : Visual C + + 接受 C + + 98异常规范语法,但只是忽略了异常规范。

C + + 03只是 C + + 98的技术勘误。C + + 03中唯一真正新的是 值初始化。这和例外没有关系。

使用 C + + 11标准的一般异常规范被删除,代之以 noexcept关键字。

C + + 11标准还增加了对存储和重新引发异常的支持,这对于在 C 语言回调中传播 C + + 异常非常有用。这种支持有效地约束了当前异常的存储方式。然而,据我所知,这并不会影响性能,除非在较新的代码中,异常处理可以更容易地用于 C 语言回调的两端。

是的,但那不重要。 为什么?
看看这个:
Https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx

基本上,这说明使用像 Alexandrescu 描述的异常(50倍减速,因为它们使用 catch作为 else)是错误的。 That being said for ppl who like to do it like that 我希望 C + + 22:)能添加这样的内容:
(注意,这必须是核心语言,因为它基本上是编译器从现有的代码生成)

result = attempt<lexical_cast<int>>("12345");  //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
int x = result.get(); // or result.result;
}
else
{
// even possible to see what is the exception that would have happened in original function
switch (result.exception_type())
//...


}

附注: 即使异常很慢... 如果你在执行过程中没有花很多时间在这部分代码上,那也不是问题... 例如,如果浮点除法很慢,而你让它快了4倍,那么如果你花0.3% 的时间做 FP 除法,也没有关系..。

除非将代码转换为程序集或对其进行基准测试,否则永远不能声明性能。

这是你看到的: (quick-bench)

错误代码对出现的百分比不敏感。只要不抛出异常,就会有一些开销。一旦你扔出去,痛苦就开始了。在此示例中,抛出它的情况分别为0% 、1% 、10% 、50% 和90% 。当异常在90% 的情况下被抛出时,代码的速度比异常在10% 的情况下被抛出的速度慢8倍。如您所见,异常非常缓慢。如果它们经常被抛出,就不要使用它们。如果您的应用程序没有实时要求,那么如果它们很少出现,您可以随意抛出它们。

你可以看到很多相互矛盾的观点,但最后, 例外情况是否缓慢? 我不做判断。只是观察基准。

C++ exceptions performance benchmark