程序性能提高

          在用C++写出高效地程序之前,必须认识到C++本身绝对与你所遇到的任何性能上的问题无关。如果想写出一个高效的C++程序,你必须首先能写出一个高效的算法。在一个程序中了解80-20准则,该准则说的是大约20%的代码使用了80%的程序资源,至于那20% 的代码占据了80% 的资源,检测的最好方法不是靠经验与直觉而是利用性能检测工具,比如VS2010中自带的profiler工具。

懒惰计算法(lazy evaluation):将推迟计算工作直到系统需要这些计算的结果时进行在进行计算。在以下四种情况下注意懒惰计算法的使用:

1、引用计数:除非你确实需要,不去为任何东西制作拷贝,而是尽量使用引用计数使资源进行共享,我们应该是懒惰的,只要可能就共享使用其它值。

2、区别对待读调用和写调用,因为读取对象资源是很容易的无需修改,而写入这个对象资源则需要在写入前对该对象资源值制作一个新拷贝。

3、懒惰提取Lazy Fetching):假设你的程序使用了一些包含许多字段的大型对象,这些对象的生存期超越了程序运行期,所以它们必须被存储在数据库里。每一个大型对象都有一个唯一的对象标识符,用来从数据库中重新获得对象。为这样的对象获取所有的数据,数据库的操作的开销将非常大,特别是如果从远程数据库中获取数据和通过网络发送数据时。而在这种情况下,不需要读去所有数据,只需要根据实际需要进行懒惰提取当前需要的资源。

4、懒惰表达式计算(Lazy Expression Evaluation):在数字程序(矩阵乘以矩阵的运算)中,避免不必要的表达式运算。

总之,以上这四个方面展示了lazy evaluation在各个领域都是有用的:能避免不需要的对象拷贝,通过使用operator[]区分出读操作,避免不需要的数据库读取操作,避免不需要的数字操作。

过度热情计算法(over-eager evaluation):该方法的思路与懒惰计算法的思路相反,该方法指:在要求你做某些事情以前就完成它们。通过over-eager方法分摊预期计算的开销,例如caching和prefething,这并不与我在条款M17中提出的有关lazy evaluation的建议相矛盾。当你必须支持某些操作而不总需要其结果时,lazy evaluation是在这种时候使用的用以提高程序效率的技术。当你必须支持某些操作而其结果几乎总是被需要或被不止一次地需要时,over-eager是在这种时候使用的用以提高程序效率的一种技术。它们所产生的巨大的性能提高证明在这方面花些精力是值得的。

以上两种方法适用于所以的语言,与语言本身无关(独立于语言本身的性质),而下面讨论的几点则是针对于C++本身,在C++程序中理解临时对象的来源、协助完成返回值的优化、通过重载避免隐式类型转换、考虑使用运算符的赋值形式取代单独形式、考虑变更程序库以及理解虚函数、多继承虚基类和TRRI多需的代价。

1、理解临时对象产生的来源:在C++中真正的临时对象是看不见的,它们不出现在你的源代码中。建立一个没有命名的非堆(non-heap)对象会产生临时对象。临时对象产生的两种情况,1、在函数调用时,传送给函数的对象类型与参数类型不匹配时,为成功能调用该函数,参数间需要进行类型转换;2、函数在进行返回对象时。在函数参数不一致进行隐式类型转换,产生临时对象时,注意:仅当通过传值(by value)方式传递对象或传递常量引用(reference-to-const)参数时,才会发生这些类型转换。当传递一个非常量引用(reference-to-non-const)参数对象,就不会发生。总之,临时对象是有开销的,所以你应该尽可能地去除它们,然而更重要的是训练自己寻找可能建立临时对象的地方。在任何时候只要见到常量引用(reference-to-const)参数或传值参数,就存在建立临时对象而绑定在参数上的可能性。在任何时候只要见到函数返回对象,就会有一个临时对象被建立(以后被释放)。学会寻找这些对象构造,你就能显著地增强透过编译器表面动作而看到其背后开销的能力。

2、协助完成返回值的优化:在一些场合下,函数的返回值只能是传值方式而不能是引用或指针,在这种情况下(即函数通过传值方式(by value)返回一个对象时)不可避免地要生成一个临时对象,此时解决的问题是:以某种方法返回对象,能让编译器消除临时对象的开销,这样编写函数通常是很普遍的,这种技巧是返回constructor argument而不是直接返回对象。

3、通过重载避免隐式类型转换:

Object obj2 = obj1 + 10;首先以10为参数调用类Object的构造函数产生一个临时对象,之后跟obj1进行相加操作;如果重载函数operator+(Object,int)存在的话,就不会产生临时对象了。重载时要注意:在C++中有一条规则是每一个重载的operator必须带有一个用户定义类型(user-defined type)的参数。

4、考虑用运算符的赋值形式(op=)取代其单独形式(op):总的来说operator的赋值形式比其单独形式效率更高,因为单独形式要返回一个新对象,从而在临时对象的构造和释放上有一些开销。operator的赋值形式把结果写到左边的参数里,因此不需要生成临时对象来容纳operator的返回值。

5、考虑变更程序库:比如如果你的程序有I/O瓶颈,你可以考虑用stdio替代iostream,如果程序在动态分配和释放内存上使用了大量时间,你可以想想是否有其他的operator new 和 operator delete的实现可用。因为不同的程序库在效率、可扩展性、移植性、类型安全和其他一些领域上蕴含着不同的设计理念,通过变换使用给予性能更多考虑的程序库,你有时可以大幅度地提高软件的效率。

6、理解虚拟函数、多继承、虚基类和RTTI所需的代价:C++编译器们必须实现语言的每一个特性。这些实现的细节当然是由编译器来决定的,并且不同的编译器有不同的方法实现语言的特性。在多数情况下,你不用关心这些事情(因为这是编译器的事情,人为干预不了)。理解虚函数、多继承、虚基类、RTTI所需的代价是重要的,但是如果你需要这些功能,不管采取什么样的方法你都得为此付出代价,然而有些特性的实现对对象大小和其成员函数执行速度有很大的影响,所以对于这些特性有一个基本的了解,知道编译器可能在背后做了些什么,就显得很重要。在查看子类的对象内存布局时可以发现虚函数、多继承和虚基类、RTTI能使对象变得更大,因为他们额外的增加了一些指针以指向虚函数和一些运行时类与对象的信息。

注意:一、当调用一个虚拟函数时,被执行的代码必须与调用函数的对象的动态类型相一致;指向对象的指针或引用的类型是不重要的。二、虚函数不能是内联的。这是因为“内联”是指“在编译期间用被调用的函数体本身来代替函数调用的指令,”但是虚函数的“虚”是指“直到运行时才能知道要调用的是哪一个函数。”



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值