C++中的异常是否真的很慢

Avi*_*ash 83 c++

我正在观看C++中的系统错误处理 - Andrei Alexandrescu他声称这Exceptions in C++非常非常慢.

我想知道这是否仍然适用 C++98

Mat*_* M. 145

今天用于例外的主要模型(Itanium ABI,VC++ 64位)是零成本模型例外.

我们的想法是,通过设置一个保护并明确检查各处是否存在异常而不是浪费时间,编译器会生成一个边表,将可能引发异常的任何点(程序计数器)映射到处理程序列表.抛出异常时,会查询此列表以选择正确的处理程序(如果有)并解除堆栈.

与典型if (error)策略相比:

  • 顾名思义,零成本模型在没有异常发生时是免费的
  • if当发生异常时,它的成本约为10倍/ 20倍

然而,成本并非无足轻重:

  • 边桌通常是冷的,因此从记忆中取出它需要很长时间
  • 确定正确的处理程序涉及RTTI:许多RTTI描述符用于获取,分散在内存中,以及要运行的复杂操作(基本上是dynamic_cast对每个处理程序的测试)

因此,主要是缓存未命中,因此与纯CPU代码相比并不是微不足道的.

注意:有关更多详细信息,请阅读TR18015报告,第5.4章异常处理(pdf)

所以,是的,异常路径上的异常很慢,但它们通常比明确的检查(if策略)更快.

注意:根据NoSenseEtAl,Andrei Alexandrescu似乎质疑这个"更快".我个人测量了我的程序中的加速,我还没有看到关于失去可优化性的证据.


有关系吗 ?

我会声称它没有.程序的编写应考虑到可读性,而不是性能(至少不是第一个标准).当人们期望呼叫者不能或不希望当场处理故障并将其传递到堆栈时,将使用例外.额外奖励:在C++ 11中,可以使用标准库在线程之间编组异常.

这虽然很微妙,我声称不map::find应该扔但是如果尝试取消引用它会失败因为它是null,我会很好地map::find返回一个checked_ptr抛出:在后一种情况下,如在Alexandrescu引入的类的情况下,调用者选择在显式检查和依赖异常之间.赋予呼叫者权力而不给予他更多责任通常是良好设计的标志.

  • >顾名思义,零成本模型在没有异常发生时是免费的,这实际上并不是最精细的细节.生成更多代码总是会对性能产生影响,即使是小而微妙的......操作系统加载可执行文件可能需要更长的时间,否则您将获得更多的i-cache未命中.还有,堆栈展开代码怎么样?另外,你可以用什么样的实验来衡量效果,而不是试图用理性思考来理解它? (4认同)
  • +1我只会添加四件事:(0)关于在C++ 11中添加的重新抛出的支持; (1)提及委员会关于c ++效率的报告; (2)关于正确性的一些评论(特别是可读性); (3)关于性能的评论,关于不使用例外的情况下的评价(全部是相对的) (2认同)
  • @ Cheersandhth.-Alf:(0),(1)和(3)完成:谢谢.关于正确性(2),虽然它胜过可读性,但我不确定导致代码比其他错误处理策略更正确的异常(它很容易忘记执行异常创建的许多不可见路径). (2认同)
  • 描述可能是本地正确的,但可能值得注意的是,异常的存在对编译器可以做出的假设和优化具有全局影响.这些影响遇到的问题是它们"没有琐碎的反例",因为编译器总能看到一个小程序.在有或没有例外的情况下对现实的大型代码库进行概要分析可能是一个好主意. (2认同)
  • @jheriko:实际上,我相信我已经解决了您的大多数问题。加载时间不应该受到影响(不应该加载冷代码),不应该影响i高速缓存(不应该将其放入i高速缓存中),因此解决一个遗漏的问题: “如何测量” =>用对`abort`的调用替换抛出的任何异常,将使您能够测量二进制大小的占用空间,并检查加载时间/ i缓存的行为是否类似。当然,最好不要点击任何“中止” ... (2认同)
  • @NoSenseEtAl,我相信,Aleandrescu 声称的异常性能损失为 7%,是为了比较 C++ `f(); G(); H(); ...` 其中 `f`、`g`、`h` 可能会抛出,因此不会发生重新排序,与 C `f(); G(); h();...` 他们不扔的地方。什么@MatthieuM。正在谈论用 C++ 编写`f() 是否更快;G(); H(); ...` 比在 C++ 中`if (f() == -1) return -1; 如果 (g() == -1) 返回 -1; 如果 (h() == -1) 返回 -1;`。关键是,如果您正在使用 C++ 并启用异常(如果您现在不这样做,STL 将无法工作),7% 的惩罚是不可避免的。在这种情况下,使用异常比返回码更快 (2认同)
  • @itzJanuary:仅仅因为有一个 try 块并不意味着它的 catch 适合将要抛出的异常;并且由于可以抛出多个异常(想想 `std::bad_alloc`、`std::out_of_range` 和自定义的 `ClientNotFound`),“匹配”catch 块可能会因每个异常而有所不同。再加上您可能会尝试调用闭源第三方库或动态链接库,并且您无法在编译时知道将抛出哪个异常。 (2认同)

Che*_*Alf 56

当问题发布后,我正在去医生的路上,有一辆出租车在等,所以我只有时间做一个简短的评论.但现在评论,投票和投票,我最好添加自己的答案.即使Matthieu的答案已经很好了.


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

重申索赔

"我正在观看C++中的系统错误处理 - Andrei Alexandrescu他声称C++中的异常非常缓慢."

如果这确实是安德烈所说的话,那么一旦他非常误导,即使不是彻头彻尾的错误.对于引发/抛出的异常,与语言中的其他基本操作相比总是很慢,无论编程语言如何.正如声称的声明所表明的那样,在C++中,不仅仅是在C++中,而是在其他语言中.

一般来说,无论语言多少,两种基本语言特征都比其他语言慢几个数量级,因为它们转换为处理复杂数据结构的例程调用,

  • 例外投掷,和

  • 动态内存分配.

令人高兴的是,在C++中,人们通常可以避免使用时间要求严格的代码.

不幸的,即使C++的默认效率非常接近,也不会有免费午餐.:-)为了通过避免异常抛出和动态内存分配获得的效率通常通过在较低的抽象级别进行编码来实现,使用C++作为"更好的C".较低的抽象意味着更大的"复杂性".

更高的复杂性意味着更多的时间花在维护上,代码重用很少或没有好处,即使难以估计或衡量,也是真正的货币成本.也就是说,使用C++,如果需要的话,可以将一些程序员效率交换为执行效率.是否这样做主要是工程和直觉决定,因为在实践中,只能获得收益,而不是成本,可以轻松估算和衡量.


是否存在C++异常抛出性能的客观测量?

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


异常"缓慢"是什么意思

主要意味着,由于搜索处理程序,a throw可以采用与例如int分配相比的非常长时间.

正如TR18015在5.4节"异常"中讨论的那样,有两个主要的异常处理实现策略,

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

  • 编译器生成静态查找表的方法,该查找表用于确定抛出异常的处理程序.

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

此报告也讨论过,对于每种方法,有三个主要方面,例外处理对效率的影响:

  • try-blocks,

  • 常规功能(优化机会),以及

  • throw-expressions.

主要是,使用动态处理程序方法(32位Windows)异常处理会对try块产生影响,主要是与语言无关(因为这是Windows的结构化异常处理方案所强制的),而静态表方法的成本大致为零try-块.讨论这将需要更多的空间和研究,而不是实际的答案.因此,请参阅报告以获取详细信息.

不幸的是,从2006年开始,这份报告在2012年底已经有点过时了,据我所知,没有任何可比性更新的报道.

另一个重要的观点是,使用例外对绩效的影响与支持语言特征的孤立效率有很大不同,因为正如报告所述,

"在考虑异常处理时,必须将其与处理错误的其他方法进行对比."

例如:

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

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

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

该报告有一个不同的方面列表需要考虑,但无论如何,获取有关执行效率的硬性事实的唯一实用方法可能是使用异常实现相同的程序,而不是使用异常,在开发时间的决定上限内,以及开发人员熟悉每种方式,然后测量.


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

正确性几乎总是胜过效率.

没有例外,很容易发生以下情况:

  1. 一些代码P用于获取资源或计算一些信息.

  2. 调用代码C应检查成功/失败,但不检查.

  3. 在C之后的代码中使用不存在的资源或无效信息,导致一般混乱.

主要问题是第(2)点,其中使用通常的返回码方案,不强制调用代码C.

有两种主要方法可以强制进行此类检查:

  • P在失败时直接抛出异常的地方.

  • 其中P返回C 在使用其主值之前必须检查的对象(否则是异常或终止).

第二种方法是AFAIK,首先由Barton和Nackman在他们的书" 科学与工程C++:高级技术与实例的介绍"中描述,他们在这本书中引入了一个称为Fallow"可能"功能结果的类.optional现在,Boost库提供了一个类似的类.并且您可以Optional自己轻松地实现一个类,使用std::vectoras值载体作为非POD结果的情况.

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


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

"我想知道这对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语言回调的两端使用.

  • "与语言中的其他基本操作相比,异常总是很慢,无论编程语言如何"......除了用于将异常编译成普通流控制的语言之外. (5认同)
  • @JonHarrop:你*听起来像是一个两难的境地.但它与目前为止所讨论的任何内容都没有任何关联,到目前为止,你已经发布了一长串负面的**废话**.我必须相信你是为了同意或不同意一些模糊的措辞,因为作为对手,你选择你将揭示它"意味着什么",我当然*不要*信任你所有那些毫无意义的废话,downvoting等等 (4认同)
  • "抛出异常涉及分配和堆栈展开".一般来说,这显然也不正确,OCaml也是一个反例.在垃圾收集语言中,没有必要展开堆栈,因为没有析构函数所以你只需要"longjmp"到处理程序. (3认同)
  • @JonHarrop:大概你不知道Pyhon有一个用于异常处理的finally子句.这意味着Python实现要么具有堆栈展开,要么不具有Python.你似乎完全不知道你所做的(幻想)主张的主题.抱歉. (2认同)
  • @ Cheersandhth.-Alf:“ Pyhon拥有处理异常的最终条款。这意味着Python实现要么具有堆栈展开功能,要么不是Python”。可以实现try..finally构造而无需展开堆栈。F#,C#和Java都实现了try..finally而不使用堆栈展开。您只需将longjmp传递给处理程序(正如我已经解释的那样)。 (2认同)
  • @JonHarrop:我应该提到,在堆栈中执行“finally”块(包括“with”语句中的隐式块)会向上调用堆栈,在到达异常处理程序的过程中,*是*堆栈展开。从逻辑上讲,不进行堆栈展开就不可能进行堆栈展开。你又胡言乱语了,抱歉。 (2认同)
  • @ Cheersandhth.-Alf:你接受与否?是还是不是?http://www.cesura17.net/~will/professional/research/papers/tail.pdf (2认同)
  • 我意识到这是旧的,但似乎上面的整个论点都是基于一个人做了一些一般性的陈述,因为他们知道你给数据结构的名字暗示了它的用法和能力,而不是它的实现细节。 ......而另一个人*强烈*(如果你问我的话太强烈了)认为如果你不说鸭子的种类,那么你还不如称它为鹅?“你接受你必须称它为野鸭吗?你不能称野鸭为鸭子 羽耳哨子也是鸭子。” (2认同)

Phi*_*ipp 11

这取决于编译器.

例如,GCC在处理异常时表现非常糟糕,但在过去几年中这种情况要好得多.

但请注意,处理异常应该 - 正如名称所示 - 是例外,而不是软件设计中的规则.当你的应用程序每秒抛出如此多的异常会影响性能时,这仍然被视为正常操作,那么你应该考虑采取不同的方式.

通过将所有笨重的错误处理代码排除在外,异常是使代码更具可读性的好方法,但只要它们成为正常程序流程的一部分,它们就变得非常难以理解.请记住,a throw几乎是goto catch伪装的.

  • C++ 98是ISO标准,而不是编译器.有许多编译器实现它. (5认同)
  • @thecoshman:不会.C++标准没有说明应该如何实现(除了标准的"实施限制"部分之外). (2认同)
  • @Insilico然后我只能得出逻辑结论(令人震惊地)它是实现定义(读取,编译器特定)异常如何执行. (2认同)

Ara*_*ash 6

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

您会看到以下内容:(快速工作台)

错误代码对出现的百分比不敏感。只要不抛出异常,它们就会产生一些开销。一旦扔掉它们,苦难就开始了。在此示例中,将为0%,1%,10%,50%和90%的情况抛出该事件。当90%的时间抛出异常时,代码比10%的时间抛出异常的速度慢8倍。如您所见,异常确实很慢。如果经常扔它们,请勿使用它们。如果您的应用程序没有实时性要求,请在很少发生的情况下随意抛出它们。

您会看到许多关于它们的矛盾意见。但最后,例外情况是否缓慢?我不判断。只是看基准。

C ++例外性能基准