浅谈C++ 异常处理的语义和性能
异常处理是个十分深奥的主题,这里只是浅论其对C++性能的影响。
在VC++中,有多个异常处理模式,三个最重要:
- No exception handling (无异常处理)
- C++ only (C++语言异常处理)
- C++ 加SEH (C++语言加windows 结构异常处理机制)
异常处理每增加一个级别,都要付出时空上的代价。我们从下面简单的C++例子着手,分析异常处理的原理及其性能:
// simple class
class MyAppObject
{
public:
MyAppObject(int id) : _myID(id) {}
~MyAppObject();
int _myID;
void DoSomething(int throwWhat) ;
};
// can throw 2 different exception
void MyAppObject::DoSomething(int throwWhat)
{
printf("MyAppObject::DoSomething called for '%d'\n", _myID);
switch (throwWhat)
{
case 0:
break;
case 1:
this->_myID /= 0; // exception 1
break;
case 2:
throw SimpleString("error!"); // exception 2
break;
}
}
// Test exception for the above class
void TestMyAppObject()
{
printf("before try”);
try // line1
{
printf("in try”);
MyAppObject so = 1; // line2
SimpleString ss("test ex point one"); // line3
so.DoSomething(1); // line4
printf("so::ID called for '%d'\n", so._myID);
MyAppObject so2 = 2; // line5
printf("so2::ID called for '%d'\n", so2._myID);
so2.DoSomething(0); // line6
}
catch(const SimpleString &e) // line7
{
//printf("something happened: %s \n", e);
}
catch(...) //line8
{
//printf("something happened: %s \n", "SEH");
}
}
第一步,我们先选择“no exception”,并将上面line1,line7,line8注释掉。代码的size是:
Exe |
Obj |
32,256 bytes |
20,931 bytes |
然而因为line4引入一个“除0”异常,我们的程序非正常地停止了工作。这并非什么大的灾难。但是如果这是关键的服务器程序,这样的结果肯定不能为客户接受。
第二步,我们选择了,C++ only flag(/EHsc)。代码size变为:
Exe |
Obj |
37,888 bytes |
24,959 bytes |
代码size较前面选择增加了近20%。
然而,这个选择决定了如果是C++的throw产生的异常我们可以俘获。操作系统产生的异常,比如windows SEH 异常机制产生的异常,也不能俘获。测试时,将line1,line7,Line8注释取消。
运行程序,“除0”异常仍然导致程序停止。然而,将line4输入改为2时,C++ throw 的异常被line7俘获。
第三步,我们选择“C++ 加 SHE (/EHa)”,代码size变为:
Exe |
Obj |
37.0 KB (37,888 bytes) |
28,486 bytes |
代码 obj size 略有变化,但是不显著。选择了这个后,MyAppObject::DoSomething的两种异常都能被俘获了。
异常处理语义
加了异常处理,程序的“工作集(working set)”, 的增长度高达20%,这是相当显著的。关键的软件部件必须考虑到这一点。那么,运行速度会不会受到影响呢?我们先看看异常处理的语义吧。
上面的TestMyAppObject中,由于C++必须保证一旦异常出现,能“正确地”地销毁自动变量,比如TestMyAppObject中的so,ss,和 so2 变量。在有异常处理的情况下,必须区分“现行程序”的“区域”和“热点”。
比如,TestMyAppObject的区域有before try 和 in try。
TestMyAppObject热点有line2 ~ line6 (每个line都是一个热点)。
TestMyAppObject异常处理的逻辑是:
- 做“stack unwinding (堆栈回滚)”:
- 如果line2出异常,无须作什么(除非有MyAppObject 里有部分未完成构造的成员partially constructed member 问题)。
- 如果line3出异常,so必须销毁。
- 如果line4出异常,so和ss都必须销毁。
- 如果line6出异常,so,ss,和so2都须销毁。
- 如果找到catch,执行catch
- 如果此函数没有catch,继续往上面函数,重复以上步骤
VC++的stack unwinding实现大致如此:
异常处理逻辑可以转换成一个静态的jump列表(列出上面的四个热点的jump to 地址),和一个stack_unwind()函数(堆栈回滚函数),根据当前的”热点”,通过此列表,动态地跳到异常处里的回滚代码处。
综合起来,异常处理在C++中,根据函数的auto变量的分布,必须在每个可能出现异常的函数添加上诉jump列表,导致程序size和工作集明显增加。但是测试表明,如果不出现异常,程序的执行速度的影响是可忽略的(仅仅需要保持热点位置),TestMyAppObject的测试结果选择异常处理(但不出异常)反而比选择不支持异常处理稍快。
出现异常后,TestMyAppObject的测试结果表明,程序速度的影响可以在10%~15%以上。但是我的测试还没有加rethrow 获者其它异常处理逻辑,仅仅俘获而已。
另一个有趣的问题是,函数中auto变量的分布,对“热点列表”size的影响, 热点太多,会导致热点列表变得很大,所以如果可能,尽量把auto变量放在顶端:
X a, b;
Y c,d;
而不是
X a;
// do something (1)
X b;
// do something else (2)
Y c;
// do yet something else (3)
Y d;
因为第一种分布只有一个热点(假设constructor 不会throw)。而第二种分布至少有三个热点。
测试结果
测试上述TestMyAppObject函数,循环1000次的结果:
- 传值0,使line4不出现异常(C++ throw),时间是0.802秒。
传值1,使line4出现除零异常,时间是0.832秒。
- 传值2,使line4出现异常(C++ throw),测试1000次测试,时间是1.043秒。
这个结果我有下列观察:
- C++ throw的代价明显高于windows SEH。C++ throw 异常在上述测试的时间比不出现异常增加近20%。但是如果我们throw简单的primitive 值,速度可能增快(读者可以自己测试)。
- 除零异常在这里和无异常速度接近,但是考虑到本测试的简单性,和实际应用中try-catch可能纵跨多个函数,会线性增加stack-unwinding的代价。所以我认为,实际结果中,如果异常出现后出现性能10%~15%下降是正常的。
- 另外要考虑的是OS和编译版本。VC++的异常处理比前面版本的性能大大提高了。
总结
异常处理是C++中具有重要附加值的语言构造,为安全可靠的应用程序提供了基石。
但是它也同时具有时空两方面的代价(trade off),我们在应用时要清楚这个方面。异常应该在“异常时”用 (好像是废话,其实是设计思想和模式的重要一环),不要把它当作方便的“控制构造 control construct”来用。如果应用容许,也要尽可能减少“热点”,减小热点列表。
浅谈C++ 异常处理的语义和性能的更多相关文章
- 浅谈Objective-C异常处理
-----<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培 ...
- 浅谈MVC异常处理
在日常开发中,我们会去捕捉很多的异常,来进行处理,通常我们的方法就是,在需要进行异常处理的地方加上 try catch 块,但是,如果需要异常处理的地方很多,那么,就会频繁的去写try catch 块 ...
- 浅谈PHP异常处理
1.PHP中异常的独特性 PHP中的异常的独特性,即PHP中的异常不同于主流语言C++.java中的异常.在Java中,异常是唯一的错误报告方式,而在PHP中却不是这样,而是把所有不正常的情况都视作了 ...
- Android性能优化的浅谈
一.概要: 本文主要以Android的渲染机制.UI优化.多线程的处理.缓存处理.电量优化以及代码规范等几方面来简述Android的性能优化 二.渲染机制的优化: 大多数用户感知到的卡顿等性能问题的最 ...
- 浅谈java性能分析
浅谈java性能分析,效能分析 在老师强烈的要求下做了效能分析,对上次写过的词频统计的程序进行分析以及改进. 对于效能分析:我个人很浅显的认为就是程序的运行效率,代码的执行效率等等. java做性能测 ...
- 浅谈DOM性能考虑
浅谈DOM性能考虑 很多人都会忽视脚本对Web应用整体性能的影响.为保证应用的流畅运行,在为文档编写和应用脚本时,需要注意一些问题.一.尽量减少访问DOM和尽量减少标记 访问DOM的方式对脚本性 ...
- 浅谈Android应用性能之内存
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 ...
- [原创]浅谈H5页面性能优化方法
[原创]浅谈H5页面性能优化方法 前阶段公司H5页面性能测试,其中测试时也发现了一些性能瓶颈问题,接下来我们在来谈谈H5页面性能优化,仅仅是一些常用H5页面性能优化措施,其实和Web页面性能优化思路大 ...
- 浅谈Spark应用程序的性能调优
浅谈Spark应用程序的性能调优 :http://geek.csdn.net/news/detail/51819 下面列出的这些API会导致Shuffle操作,是数据倾斜可能发生的关键点所在 1. g ...
随机推荐
- 权重随机算法的java实现
一.概述 平时,经常会遇到权重随机算法,从不同权重的N个元素中随机选择一个,并使得总体选择结果是按照权重分布的.如广告投放.负载均衡等. 如有4个元素A.B.C.D,权重分别为1.2.3.4,随机结果 ...
- scheme和common lisp 区别
Scheme and Common Lisp use different names for some of the basic system functions. Many Lisp program ...
- oralce 仅配置精简客户端 连接plsql ( 版本需一直,要不都是32要不是都是64)
1.Oracle服务器已经安装完成,版本10.2.0. 2.访问www.oracle.com,下载Oracle精简客户端. 下载页面地址:http://www.oracle.com/technetwo ...
- mysql 安装补充
1:假如下载的文件名为:mysql-5.0.45.tar.gz 2:假如copy到 /usr/local下 3:groupadd mysql #添加mysql组 4:useradd -g mysql ...
- Qt入门(11)——Qt插件
Qt提供了一个简单地插件接口,可以轻松地生成作为独立组件的定制数据库驱动.图象格式.文本编解码器(text codec).风格(style)和部件.警告:Qt 3.0.5对插件的一些方面做了改变,具体 ...
- 第一个ios程序
1.ios的理解: Operating System,简称OS,操作系统,ios是苹果操作系统. 2.Xcode开发环境: 苹果公司开发的编程软件,是开发人员建立OS X 和 iOS 应用程序的最快捷 ...
- Censored! - POJ 1625(ac自动机+简单dp+高精度运算)
题目大意:首先给一个字符集合,这个集合有N个字符,然后需要一个长度为M的句子,但是据子里面不能包含的串有P个,每个串里面的字符都是有字符集和里面的字符构成的,现在想知道最多能构造多少个不重复的句子. ...
- AngularJS测试二 jasmine测试路由 控制器 过滤器 事件 服务
测试应用 1.测试路由 我们需要检测路由是否在运作,是否找到了,或者是404了.我们要确认路由事件触发了,预期的模板是否真的加载了.既然路由会改变页面的地址(URL)和页面内容,我们需要检测路由是否被 ...
- Hibernate二 映射 注解 一级缓存
Hibernate映射1.@Entity 被该注解修饰的POJO类是一个实体,可以用name属性指定该实体类的名称,系统默认以该类的类名作为实体类的名称.2.@Table 指定持久化类所映射的表,它的 ...
- EntityFramework 使用Linq处理内连接(inner join)、外链接(left/right outer join)、多表查询
场景:在实际的项目中使用EntityFramework都会遇到使用Ef处理连接查询的问题,这里做一些小例子如何通过Linq语法处理内连接(inner join).外连接(left/right oute ...