我在看Systematic Error Handling in C++—Andrei Alexandrescu他声称Exceptions in C++
非常慢。
我想知道C++98
答案 0 :(得分:134)
今天用于例外的主要模型(Itanium ABI,VC ++ 64位)是零成本模型例外。
这个想法是,通过设置一个保护并明确地检查各处是否存在异常,而不是浪费时间,编译器生成一个边表,将任何可能引发异常的点(程序计数器)映射到一个列表处理程序。抛出异常时,会查询此列表以选择正确的处理程序(如果有)并且堆栈已解除。
与典型的if (error)
策略相比:
if
然而,成本并非无足轻重:
dynamic_cast
测试)因此,主要是缓存未命中,因此与纯CPU代码相比并不简单。
注意:有关详情,请参阅TR18015 report, chapter 5.4 Exception Handling (pdf)
所以,是的,例外情况在特殊路径上很慢,但它们通常比明确检查(if
策略)更快。
注意:根据NoSenseEtAl,Andrei Alexandrescu似乎质疑这个“更快”。我个人测量了我的程序中的加速,我还没有看到关于失去可优化性的证据。
重要吗?
我会声称它没有。程序的编写应考虑可读性,而不是性能(至少不是第一个标准)。当人们期望呼叫者不能或不希望当场处理故障并将其传递到堆栈时,将使用例外。 Bonus:在C ++ 11中,可以使用标准库在线程之间编组异常。
虽然这很微妙,但我声称map::find
不应抛出,但我很好map::find
返回checked_ptr
如果尝试取消引用它失败因为它为null,则抛出{{1}}:后一种情况,如Alexandrescu引入的类的情况,调用者在显式检查和依赖异常之间选择。赋予呼叫者权力而不给予他更多责任通常是良好设计的标志。
答案 1 :(得分:53)
问题发布后,我正在去医生的路上,有一辆出租车在等,所以我只有时间做一个简短的评论。但现在评论,投票和投票,我最好添加自己的答案。即使Matthieu’s answer已经相当不错了。
重申索赔
“我在看Systematic Error Handling in C++—Andrei Alexandrescu他声称C ++中的异常非常慢。”
如果这确实是安德烈所说的话,那么一旦他非常误导,即使不是彻头彻尾的错误。对于引发/抛出的异常,与语言中的其他基本操作相比总是很慢,,无论编程语言。正如声称的声明所表明的那样,在C ++中,不仅仅是在C ++中,而是在其他语言中更多。
一般来说,无论语言多少,两种基本语言特征都比其他语言慢几个数量级,因为它们转换为处理复杂数据结构的例程调用,
异常抛出,
动态内存分配。
在C ++中,人们经常可以避免使用时间要求严格的代码。
不幸的是即使C ++的默认效率非常接近,也没有像免费午餐这样的东西。 :-)为了通过避免异常抛出和动态内存分配获得的效率通常通过在较低的抽象级别进行编码来实现,使用C ++作为“更好的C”。较低的抽象意味着更大的“复杂性”。
更高的复杂性意味着更多的时间花在维护上,代码重用很少或没有好处,即使难以估计或衡量,也是真正的货币成本。也就是说,如果需要,可以使用C ++来交换一些程序员效率以实现执行效率。是否这样做主要是工程和直觉决定,因为在实践中,只有收益而不是成本,可以很容易地估计和衡量。
是的,国际C ++标准化委员会已发布Technical Report on C++ performance, TR18015。
主要意味着throw
可以采用非常长时间™,例如由于搜索处理程序而导致int
分配。
正如TR18015在5.4节“异常”中讨论的那样,有两个主要的异常处理实现策略,
每个try
- 块动态设置异常捕获的方法,以便在抛出异常时执行动态处理程序链的搜索,并且
编译器生成静态查找表的方法,该查找表用于确定抛出异常的处理程序。
第一种非常灵活和通用的方法几乎是在32位Windows中强制执行,而在64位版本和* nix-land中,通常使用第二种更有效的方法。
正如该报告所讨论的那样,对于每种方法,有三个主要领域,例外处理对效率的影响:
try
- 块,
常规功能(优化机会)和
throw
- 表达式
主要是,使用动态处理程序方法(32位Windows)异常处理会对try
块产生影响,大部分都与语言无关(因为这是Windows强制执行的结构化异常处理< / em> scheme),而静态表方法对try
- 块的成本大致为零。讨论这将需要更多的空间和研究,而不是实际的答案。因此,请参阅报告了解详情。
不幸的是,自2006年以来,该报告已经有点过时了,截至2012年底,据我所知,没有比较新的报道。
另一个重要的观点是,使用异常对性能的影响与支持语言功能的孤立效率非常不同,因为正如报告所述,
“在考虑异常处理时,必须将其与其他方式进行对比 处理错误。“
例如:
由于编程风格不同(正确性)导致的维护成本
冗余呼叫网站if
故障检查与集中式try
缓存问题(例如,较短的代码可能适合缓存)
该报告有一个不同的方面列表需要考虑,但无论如何,获取有关执行效率的事实的唯一实用方法可能是在开发时间的决定上限内使用异常而不是使用异常来实现相同的程序,并且熟悉每种方式的开发人员,然后 MEASURE 。
正确性几乎总是胜过效率。
没有例外,很容易发生以下情况:
某些代码P用于获取资源或计算某些信息。
调用代码C应检查成功/失败,但不会检查。
在C之后的代码中使用不存在的资源或无效信息,导致一般混乱。
主要问题是第(2)点,使用通常的返回代码方案,调用代码C不会被强制检查。
有两种主要方法可以强制进行此类检查:
P在失败时直接抛出异常。
其中P在使用其主值(否则为异常或终止)之前返回C必须检查的对象。
第二种方法是AFAIK,首先由Barton和Nackman在他们的书Scientific and Engineering C++: An Introduction with Advanced Techniques and Examples中描述,他们为“可能的”功能引入了一个名为 Fallow
的类。结果。 Boost库现在提供了一个名为 optional
的类似类。您可以自己轻松实施Optional
课程,使用std::vector
作为非POD结果的值载体。
使用第一种方法,调用代码C别无选择,只能使用异常处理技术。但是,使用第二种方法,调用代码C本身可以决定是否进行基于if
的检查或一般异常处理。因此,第二种方法支持使程序员与执行时间效率进行权衡。
“我想知道这对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语言回调的两端使用。
答案 2 :(得分:10)
这取决于编译器。
例如,GCC在处理异常时表现非常糟糕,但在过去的几年中这种情况要好得多。但请注意,处理异常应该 - 正如名称所示 - 是例外,而不是软件设计中的规则。当你的应用程序每秒抛出如此多的异常会影响性能时,这仍然被视为正常操作,那么你应该考虑采取不同的做法。
异常是通过将所有笨重的错误处理代码排除在外而使代码更具可读性的好方法,但只要它们成为正常程序流程的一部分,它们就变得非常难以理解。请记住,throw
几乎是伪装goto catch
。
答案 3 :(得分:4)
除非将代码转换为程序集或对其进行基准测试,否则您永远不能声称性能。
您在这里看到的是:(quick-bench)
错误代码对出现的百分比不敏感。只要不抛出异常,它们就会产生一些开销。一旦扔掉它们,苦难就开始了。在此示例中,针对0%,1%,10%,50%和90%的案例抛出该事件。
您会看到许多关于它们的矛盾意见。但是最后 例外很慢吗?我不判断。只是看基准测试。
答案 4 :(得分:2)
是的,但这没关系。
为什么呢?
阅读本文:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
基本上说,使用像Alexandrescu这样的异常描述(50倍减速,因为他们使用catch
作为else
)是错误的。
对于那些喜欢这样做的人而言
我希望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())
//...
}
P.S。还要注意,即使例外情况很慢......如果你在执行期间不花费大量时间在代码的那一部分上也不是问题...例如,如果浮动除法很慢而你的速度提高了4倍如果你花费0.3%的时间进行FP分割,这无关紧要......
答案 5 :(得分:1)
不应该例外。如果你期望它们发生,那么你的做法是错误的。
这就是为什么标准没有设置实现细节的原因(此外,它通常依赖于OS,在Win32中调用RaiseException)。因为如果它发生了,它应该不关心它有多慢。
答案 6 :(得分:0)
就像in silico所说的那样,它的实现依赖,但一般情况下,异常被认为对任何实现来说都很慢,不应该用于性能密集型代码。
编辑:我不是说根本不使用它们,但对于性能密集型代码,最好避免使用它们。