Chinaunix首页 | 论坛 | 博客
  • 博客访问: 423554
  • 博文数量: 45
  • 博客积分: 4075
  • 博客等级: 上校
  • 技术积分: 666
  • 用 户 组: 普通用户
  • 注册时间: 2007-04-24 18:09
个人简介

百度网页搜索部高级工程师 我的微博:http://weibo.com/pengwh85

文章分类

全部博文(45)

文章存档

2012年(3)

2011年(1)

2010年(19)

2009年(10)

2008年(12)

我的朋友

分类: C/C++

2009-11-12 23:56:11

条款16:记住8020准则

8020准则说的是大约20%的代码使用了80%的程序资源;大约20%的代码消耗了80%的运行时间;大约20%的代码使用了80%的内存;大约20%的代码执行80%的磁盘访问;80%的维护集中于大约20%的代码上。

 

条款17:考虑使用延迟计算

避免没有必要的对象拷贝;使用operator[]区分读操作和写操作;避免没有必要的数据库读取;以及避免没有必要的数值计算。实际上,如果你的计算都需要即时进行,那么延迟计算可能会减慢速度并增加内存的使用。因为除了进行所有你原本想要避免的计算以外,你还必须维护那些用于实现延迟计算的数据结构。

四个不同的应用场景:

         引用计数;区分读操作和写操作;延迟读取;延迟表达式求值

 

条款18:分期摊还预期的计算开销

使用即时计算(eager evaluation),我们需要在函数被调用时检测集合内所有的数值,然后返回一个合适的结果。使用延迟计算(lazy evaluation),只有确实需要函数的返回值时,我们才要求函数返回,能用来确定准确数值的数据结构。使用提前计算(over-eager evaluation),我们随时跟踪目前集合的值,当函数被调用时,我们可以不用计算就立刻返回正确的数值。

         提前计算背后的思想是,如果你预计某个计算会被频繁调用,你可以通过设计你的数据结构以更高效的方式来处理这些请求,这样就可以降低每次请求的平均开销。最简单的一种做法是缓存已经被计算过并且很可能不需要重新计算的那些值。

 

条款19:了解临时对象的来源

         C++中真正的临时对象是不可见的――它们不出现在源代码中。如果一个对象被创建,而又不是在堆上被创建,并且还没有名字,那这个对象就是临时对象。

临时对象通常出现在以下两种情况:一个是为了使函数调用能够成功而进行隐式类型转换,另外一个是函返回对象时所进行的隐式类型转换。

任何时候只要见到常量引用参数,就存在创建临时对象与这个参数相绑定的可能性。任何时候只要见到函数返回对象,就会有一个临时对象被创建(稍后被销毁)

 

条款20:协助编译器实现返回值优化

很多情况下,都有办法以某种方式返回对象,让编译器消除临时对象的开销。诀窍就是返回带有参数的构造函数而不是直接返回对象。

返回值优化(return value optimization):通过在函数返回的地方进行优化从而去除临时的局部对象(很可能是通过使用函数调用方的对象来替代这个临时对象)

 

条款21:通过函数重载避免隐式类型转换

C++要求每一个被重载的运算符必须至少要有一个参数属于用户自定义类型。通过重载来避免临时对象并不仅限于运算符函数。

         除非你有很好的理由能够确信通过函数重载去除临时对象可以对程序的整体性能带来显著的改善,否则没必要实现一大堆的重载函数。

 

条款22:考虑使用op=来取代单独的op运算符

首先,通常来说赋值形式的运算符要比单独形式的运算符效率高一些,因为单独形式的运算符总是要返回一个新的对象,我们要为此付出构建和析构一个临时对象的开销。赋值形式的运算符直接写回给运算符左边的参数,所以不需要产生一个用于保存运算符返回值的临时对象。

         临时对象的开销永远不会比有名字的对象更多,特别是使用老编译器的时候,它的耗费会更少。

         作为一个程序库的设计者,你应该提供两种形式的运算符,而作为应用程序开发者,如果性能问题的优先级很高,你应该首先考虑使用赋值形式的运算符而不是单独形式的运算符。

 

条款23:考虑使用其他等价的程序库

         提供类似功能的不同程序库通常在性能问题上采取不同的权衡措施,所以一旦你找到软件的瓶颈,你应该看看是否可以通过替换程序库来消除瓶颈。比如说,如果你的程序有I/O上的瓶颈,你可以考虑用stdio替代iostream,但是如果程序在动态内存分配和释放内存上消耗了大量的时间,你可以看看是否有其他的operator newoperator delete的实现可供使用。

         因为不同的程序库体现了不同的设计决策,这些决策包括效率、可扩展性、可移植性、类型安全和其他一些问题。通过切换到更注重性能问题的程序库,你有可能大幅度地提高软件的运行效率。

 

条款24:理解虚函数、多重继承、虚基类以及RTTI所带来的开销

         当虚函数被调用时,实际执行的代码取决于被调用对象的动态类型,而指向这个对象的指引或引用的类型是无关紧要的。

如果一个类声明了虚函数,那么这个类的每个对象都会有一个指向虚函数表的指针,它是一个被隐藏起来的数据成员,指向这个类的虚函数表。

         虚函数的三个开销:

1)         你必须为包含虚函数的类腾出额外的空间来存放虚函数表。一个类的虚函数表的大小取决于这个在所声明的虚函数的个数(包括从基类继承来的虚函数)

2)         对于包含虚函数的类,你必须为它的对象付出一定的开销来存放一个额外的指针vptr。对象尺寸的增加意味着缓存和虚拟内存的页面命中率会有所下降,这也意味着系统的页面切换可能会有所增加。

3)         必须放弃使用内联函数。虚函数在运行时刻的真正开销与内联函数有关。在实际应用中,虚函数不应该被内联。这是因为“内联”意味着“在编译时刻用被调用函数的函数体来代替被调用函数。”但是“虚函数”意味着“运行时刻决定被调用的是哪一个函数。”

虚函数调用步骤:

根据对象的vptr找到它的vtbl

vtbl里找到与被调用函数所对应的函数指针。

调用步骤2得到的指针所指向的函数。

在多重继承中,单个对象里会有多个vptr(每个基类对应一个)。除了自己的vtbl以外,还得为基类生成特殊的vtbl

         多重继承经常要求基类为虚基类。然而虚基类本身也会带来一定的开销,因为虚基类的实现经常使用指向虚基类的指针做为避免重复的手段,这样就需要在对象内部嵌入一个或者多个指针。

         通过RTTI(runtime type identification)我们可以在运行时刻找到对象和类的有关信息,这些信息被存储在type_info类型的对象里,你可以通过使用typeid操作符访问一个类的type_info对象。RTTI被设计为在类的vtbl基础上实现。

         总结虚函数、多重继承、虚基类以及RTTI所要付出的主要代价:

Feature

Increases

Size of Objects

Increases Per-Class Data

Reduces Inlining

Virtual Functions

Yes

Yes

Yes

Multiple Inheritance

Yes

Yes

No

Virtual Base Classes

Often

Sometimes

No

RTTI

No

Yes

No

 

 
阅读(1536) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~