C++代码优化经验总结_第1页
C++代码优化经验总结_第2页
C++代码优化经验总结_第3页
C++代码优化经验总结_第4页
C++代码优化经验总结_第5页
已阅读5页,还剩26页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

C++代码优化经验总结

优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法

的效率,况且我也没有这个能力。我只是想把一些能够简单的应用到你

的C++代码中的优化技术总结在这里,这样,当你遇到几种不一致的编

程策略的时候,就能够对每种策略的性能进行一个大概的估计。这也是

本文的目的之所在.

目录:

一.优化之前

二.声明的放置

三.内联函数

四.优化你的内存使用

五.速度优化

六.最后的求助

一.优化之前

在进行优化之前,我们首先应该做的是发现我们代码的瓶颈

(bottleneck)在哪里。

然而当你做这件情况的时候切忌从一个debug-version进行推断,

由于debug-version中包

含了许多额外的代码。一个debug-version可执行体要比

release-version大出40%。那些额

外的代码都是用来支持调试的,比如说符号的查找。大多数实现都

为debug-version与rele

ase-version提供了不一致的operatornew与库函数。而且,一个

release-version的拉I彳亍

体可能已经通过多种途径进行了优化,包含不必要的临时对象的消

除,循环展开,把对象

移入寄存器,内联等等。

另外,我们要把调试与优化区分开来,它们是在完成不一致的

任务。debug-version是

用来追捕bugs与检查程序是否有逻辑上的问题。release-version

则是用来做一些性能上

的调整与进行优化。

下面就让我们来看看有什么代码优化技术吧!

二.声明的放置

程序中变量与对象的声明放在什么位置将会对性能产生显著影

响。同样,对postfix与

prefix运算符的选择也会影响性能,这一部分我们集中讨论四个问

题:初始化v.s赋值,

在程序确实要使用的地方放置声明,构造函数的初始化列表,prefix

v.spostfix运算符

O

(1)请使用初始化而不是赋值

在C语言中只同意在一个函数体的开头进行变量的声明,然而

在C++中声明能够出现在

程序的任何位置。这样做的目的是希望把对象的声明拖延到确实要

使用它的时候再进行。

这样做能够有两个好处:1.确保了对象在它被使用前不可能被程序

的其他部分恶意修改。如

果对象在开头就被声明然而却在20行以后才被使用的话,就不能做

这样的保证。2.使我们

voiduse()

Ccl;

if(is_C_Needed()二二false)

(

return;//clwasnotneeded

)

//useclhere

return;

上面这段代码中对象cl即使在有可能不使用它的情况下也会被创

建,这样我们就会为它付

出不必要的花费,有可能你会说一个对象cl能浪费多少时间,但是

假如是这种情况呢:C

cl[1000];我想就不是说浪费就浪费了。但是我们能够通过移动声明

cl的位置来改变这种情

况:

voiduse()

(

if(is_C_Needed()二二false)

(

return;//clwasnotneeded

)

Ccl;//movedfromtheblock'sbeginning

//useclhere

return;

)

怎么样,程序的性能是不是已经得到很大的改善了呢?因此请认真

分析你的代码,把声明

放在合适的位置上,它所带来的好处是你难以想象的。

(3)初始化列表

我们都明白,初始化列表通常是用来初始化const或者者

reference数据成员。但是由于

他自身的性质,我们能够通过使用初始化列表来实现性能的提升。

我们先来看一段程序:

classPerson

(

private:

Cc_l;

Cc_2;

public:

Person(constC&cl,constC&c2):c_l(cl),c_2(c2){}

};

当然构造函数我们也能够这样写:

Person::Person(constC&cl,constC&c2)

c_l=cl;

c_2=c2;

}

那么毕竟二者会带来什么样的性能差异呢,要想搞清晰这个问题,

我们首先要搞清晰二者

是如何执行的,先来看初始化列表:数据成员的声明操作都是在构

造函数执行之前就完成

了,在构造函数中往往完成的只是赋值操作,然而初始化列表直接

是在数据成员声明的时

候就进行了初始化,因此它只执行了一次copyconstructor。再来

看在构造函数中赋值的

情况:首先,在构造函数执行前会通过defaultconstructor创建

数据成员,然后在构造函

数中通过operator=进行赋值。因此它就比初始化列表多进行了一

次函数调用。性能差异

就出来了。但是请注意,假如你的数据成员都是基本类型的话,那

么为了程序的可读性就

不要使用初始化列表了,由于编译器对两者产生的汇编代码是相同

的。

(4)postfixVSprefix运算符

prefix运算符++与一比它的postfix版本效率更高,由于当

postfix运算符被使用的时

候,会需要一个临时对象来储存改变往常的值。关于基本类型,编

译器会消除这一份额外

的拷贝,但是关于用户定义类型,这大概是不可能的。因此请你尽

可能使用prefix运算符

O

三.内联函数

内联函数既能够去除函数调用所带来的效率负担又能够保留通

常函数的优点。然而,

内联函数并不是万能药,在一些情况下,它甚至能够降低程序的性

能。因此在使用的时候

应该慎重。

1.我们先来看看内联函数给我们带来的好处:从一个用户的角度来

看,内联函数看起来与

普通函数一样,它能够有参数与返回值,也能够有自己的作用域,

然而它却不可能引入通常

函数调用所带来的负担。另外,它能够比宏更安全更容易调试。

当然有一点应该意识到,inlinespecifier仅仅是对编译器的

建议,编译器有权利忽

略这个建议。那么编译器是如何决定函数内联与否呢?通常情况下

关键性因素包含函数体

的大小,是否有局部对象被声明,函数的复杂性等等。

2.那么假如一个函数被声明为inline但是却没有被内联将会发生

什么呢?理论上,当编译

器拒绝内联一个函数的时候,那个函数会像普通函数一样被对待,

但是还会出现一些其他

的问题。比如下面这段代码:

//Time,h

#include<ctime>

#include<iostream>

usingnamespacestd;

classTime

I

public:

inlinevoidShow(){for(inti=0;i<10;i+i)

cout<<time(0)<<endl;}

);

由于成员函数Time::Show。包含一个局部变量与一个for循环,因

此编译器通常拒绝inline

,同时把它当作一个普通的成员函数。但是这个包含类声明的头文

件会被单独的#include

进各个独立的编译单元中:

//fl.cpp

^11i•nciludie〃np1•ime.1hj•〃

voidfl()

Timetl;

tl.Show();

)

//f2.cpp

Sinclude"Time.h〃

voidf2()

(

Timet2;

t2.Show();

}

结果编译器为这个程序生成了两个相同成员函数的拷贝:

voidfl();

voidf2();

intmainO

fl();

f2();

return0;

}

当程序被链接的时候,linker将会面对两个相同的

Time:Show()拷贝,因此函数重定

义的连接错误发生。但是老一些的C++实现应付这种情况的办法是

通过把一个un-inlined函

数当作static来处理。因此每一份函数拷贝仅仅在自己的编译单元

中可见,这样链接错误

就解决了,但是在程序中却会留下多份函数拷贝。在这种情况下,

程序的性能不但没有提

升,反而增加了编译与链接时间与最终可执行体的大小。

但是幸运的是,新的C++标准中关于un-inlined函数的说法已

经改变。一个符合标准C+

+实现应该只生成一份函数拷贝。然而,要想所有的编译器都支持这

一点可能还需要很长时

间。

另外关于内联函数还有两个更令人头疼的问题。第一个问题是

该如何进行保护。一个

函数开始的时候可能以内联的形式出现,但是随着系统的扩展,函

数体可能要求添加额外

的功能,结果内联函数就变得不太可能,因此需要把inline

specifier去除与把函数体

放到一个单独的源文件中。另一个问题是当内联函数被应用在代码

库的时候产生。当内联

函数改变的时候,用户务必重新编译他们的代码以反映这种改变。

然而关于一个非内联函

数,用户仅仅需要重新链接就能够了。

这里想要说的是,内联函数并不是一个增强性能的灵丹妙药。

只有当函数非常短小的

时候它才能得到我们想要的效果,但是假如函数并不是很短而且在

很多地方都被调用的话

,那么将会使得可执行体的体积增大。最令人烦恼的还是当编译器

拒绝内联的时候。在老

的实现中,结果很不尽人意,尽管在新的实现中有很大的改善,但

是仍然还是不那么完善

的。一些编译器能够足够的聪明来指出什么函数能够内联什么不能,

但是,大多数编译器

就不那么聪明了,因此这就需要我们的经验来推断。假如内联函数

不能增强行能,就避免

使用它!

四.优化你的内存使用

通常优化都有几个方面:更快的运行速度,有效的系统资源使

用,更小的内存使用。

通常情况下,代码优化都是试图在以上各个方面进行改善。重新放

置声明技术被证明是消

除多余对象的建立与销毁,这样既减小了程序的大小又加快了运行

速度。然而其他的优化

技术都是基于一个方面-----更快的速度或者者是更小的内存使

用。有的时候,这些目标是互斥

的,压缩了内存的使用往往却减慢了代码速度,快速的代码却又需

要更多的内存支持。下

面总结两种在内存使用上的优化方法:

1.BitFields

在C/C++中都能够存取与访问数据的最小构成单元:bito由于

bit并不是C/C++基本的

存取单元,因此这里是通过牺牲运行速度来减少内存与辅助存储器

的空间的使用。注意:

一些硬件结构可能提供了特殊的处理器指令来存取bit,因此bit

fields是否影响程序的速

度取决于具体平台。

在我们的现实生活中,一个数据的许多位都被浪费了,由于某

些应用根本就不可能有那

么大的数据范围。也许你会说,bit是如此之小,通过它就能减小

存储空间的使用吗?的确

,在数据量很小的情况下不可能看出什么效果,但是在数据量惊人

的情况下,它所节约的空

间还是能够让我们的眼睛为之一亮的。也许你又会说,现在内存与

硬盘越来越便宜,何苦

要费半天劲,这省不了几个钱。但是还有另外一个原因一定会使你

信服,那就是数字信息

传输。一个分布式数据库都会在不一致的地点有多份拷贝。那么数

百万的纪录传输就会显得

十分昂贵。Ok,现在我们就来看看该如何做吧,首先看下面这段代

码:

structBillingRec

longcust_id;

longtimestamp;

enumCallType

toll_free,

local,

regional,

longdistance,

international,

cellular

}type;

enumCallTariff

off_peak,

medium_rate,

peaktime

}tariff;

};

上面这个结构体在32位的机器上将会占用16字节,你会发现其中

有许多位都被浪费了,尤

其是那两个enum型,浪费更是严重,因此请看下面做出的改进:

structBillingRec

{

intcusjid:24;//23bits+1signbit

inttimestamp:24;

enumCallType

{//...

};

enumCallTariff

{〃…

);

unsignedcall:3;

unsignedtariff:2;

};

现在一个数据从16字节缩减到了8字节,减少了一半,怎么样,效

果还是显著的吧:)

2.Unions

Unions通过把两个或者更多的数据成员放置在相同地址的内

存中来减少内存浪费,这就

要求在任何时间只能有一个数据成员有效。Union能够有成员函数,

包含构造函数与析构

函数,但是它不能有虚函数。C++支持anonymousunionsoanonymous

union是一个未命名

类型的未命名对象。比如:

union{longn;void*p};//anonymous

n=1000L;//membersaredirectlyaccessed

p=0;//nisnowalso0

不像命名的union,它不能有成员函数与非public的数据成员。

那么unions什么时候是有用的呢?下面这个类从数据库中获取一

个人的信息。关键字既可

以是一个特有的ID或者者人名,但是二者却不能同时有效:

classPersonalDetails

!

private:

char*name;

longID;

//...

public:

PersonalDetails(constchar*nm);//keyisoftypechar

*used

PersonalDetails(longid):ID(id){}//numerickeyused

};

上面这段代码中就会造成内存的浪费,由于在一个时间只能有一个

关键字有效。anonymous

union能够在这里使用来减少内存的使用,比如:

classPersonalDetails

{

private:

union//anonymous

(

char*name;

longID;

);

public:

PersonalDetails(constchar*nm);

PersonalDetails(longid):ID(id){/**/}//direct

accesstoamember

//...

};

通过使用union,PersonalDetails类的大小被减半。但是这里要说

明的是,节约4个字节

内存并不值得引入union所带来的烦恼,除非这个类作为数百万数

据库记录的类型或者者纪录

在一条很慢的通信线路传输。值得注意的是unions并不引入任何运

行期负担,因此这里不

会有什么速度上的缺失。anonymousunion的优点就是它的成员能

够被直接访问。

五.速度优化

在一些对速度要求非常苛刻的应用系统中,每一个CPU周期都

是要争取的。这个部分展

现了一些简单方法来进行速度优化。

1.使用类来包裹长的参数列表

一个函数调用的负担将会随着参数列表的增长而增加。运行时

系统不得不建立堆栈来

存储参数值;通常,当参数很多的时候,这样一个操作就会花费很

长的时间。

把参数列表包裹进一个单独的类中同时通过引用进行传递,这

样将会节约很多的时间

O当然,假如函数本身就很长,那么建立堆栈的时间就能够忽略了,

因此也就没有必要这

样做。然而,关于那些执行时间很短而且经常被调用的函数来说,

包裹一个长的参数列表

在对象中同时通过引用传递将会提高性能。

2.寄存器变量

registerspecifier被用来告诉编译器一个对象将被会非常多

的使用,能够把它放入

寄存器中。比如:

voidf()

int*p=newint[3000000];

registerint*p2=p;//storetheaddressinaregister

for(registerintj=0;j<3000000;j++)

(

*p2++=0;

)

//...usep

delete口p;

循环计数是应用寄存器变量的最好的候选者。当它们没有被存

入一个寄存器中,大部

分的循环时间都被用在了从内存中取出变量与给变量赋新值上。假

如把它存入一个寄存器

中的话,将会大大减少这种负担。需要注意的是,register

specifier仅仅是对编译器的

一个建议。就好比内联函数一样,编译器能够拒绝把一个对象存储

到寄存器中。另外,现

代的编译器都会通过把变量放入寄存器中来优化循环计数。

Registerstoragespecifier

并不仅仅局限在基本类型上,它能够被应用于任何类型的对象。假

如对象太大而不能装进

寄存器的话,编译器仍然能够把它放入一个高速存储器中,比如

cacheo

用registerstoragespecifier声明函数型参将会是建议编译

器把实参存入寄存器中

而不是堆栈中。比如:

voidf(registerintj,registerDated);

3.把那些保持不变的对象声明为const

通过把对象声明为const,编译器就能够利用这个声明把这样

一个对象放入寄存器中。

4.Virtualfunction的运行期负担

当调用一个virtualfunction,假如编译器能够解决调用的静

态化,将不可能引入额外

的负担。另外,一个非常短的虚函数能够被内联处理。在下面这个

例子中,一个聪明的编

译器能够做到静态调用虚函数:

ttinclude<iostream>

usingnamespacestd;

classV

{

public:

virtualvoidshow()const{cout<</zI,mV?,<<endl;}

};

classW:publicV

public:

voidshow()const{cout〈〈〃I'niWz/<<endl;}

};

voidf(V&v,V*pV)

(

v.show();

pV->show();

)

voidg()

{

Vv;

f(v,&v);

}

intmain()

g();

return0;

假如整个程序出现在一个单独的编译单元中,编译器能够对

main。中的g()进行内联替

换。同时在g()中f()的调用也能够被内联处理。由于传给f()的参

数的动态类型能够在编译

期被知晓,因此编译器能够把对虚函数的调用静态化。但是不能保

证每个编译器都这样做

o然而,一些编译器确实能够利用在编译期获得参数的动态类型从

而使得函数的调用在编

译期间就确定r下来,避免了动态绑定的负担。

5.FunctionobjectsVSfunctionpointers

用functionobjects取代functionpointers的好处不仅仅局限在

能够泛化与简

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论