版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C++代码优化方法总结优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法的效率,况且我也没有这个能力。我只是想把一些可以简单的应用到你的C++代码中的优化技术总结在这里,这样,当你遇到几种不同的编程策略的时候,就可以对每种策略的性能进行一个大概的估计。这也是本文的目的之所在。优化之前在进行优化之前,我们首先应该做的是发现我们代码的瓶颈(bottleneck)在哪里。然而当你做这件事情的时候切忌从一个debug-version进行推断,因为debug-version中包含了许多额外的代码。一个debug-version可执行体要比release-version大出40%。那些额外的代码都是用来支持调试的,比如说符号的查找。大多数实现都为debug-version和release-version提供了不同的operatornew以及库函数。而且,一个release-version的执行体可能已经通过多种途径进行了优化,包括不必要的临时对象的消除,循环展开,把对象移入寄存器,内联等等。另外,我们要把调试和优化区分开来,它们是在完成不同的任务°debug-version是用来追捕bugs以及检查程序是否有逻辑上的问题。release-version则是用来做一些性能上的调整以及进行优化。下面就让我们来看看有哪些代码优化技术吧:声明的放置程序中变量和对象的声明放在什么位置将会对性能产生显著影响。同样,对postfix和prefix运算符的选择也会影响性能。这一部分我们集中讨论四个问题:初始化v.s赋值,在程序确实要使用的地方放置声明,构造函数的初始化列表,prefixv.spostfix运算符。(1)请使用初始化而不是赋值在C语言中只允许在一个函数体的开头进行变量的声明,然而在C++中声明可以出现。在程序的任何位置。这样做的目的是希望把对象的声明拖延到确实要使用它的时候再进行。这样做可以有两个好处:1.确保了对象在它被使用前不会被程序的其他部分恶意修改。如果对象在开头就被声明然而却在20行以后才被使用的话,就不能做这样的保证。2.使我们有机会通过用初始化取代赋值来达到性能的提升,从前声明只能放在开头,然而往往开始的时候我们还没有获得我们想要的值,因此初始化所带来的好处就无法被应用。但是现在我们可以在我们获得了想要的值的时候直接进行初始化,从而省去了一步。注意,或许对于基本类型来说,初始化和赋值之间可能不会有什么差异,但是对于用户定义的类型来说,二者就会带来显著的不同,因为赋值会多进行一次函数调用----operator=。因此当我们在赋值和初始化之间进行选择的话,初始化应该是我们的首选。(2)把声明放在合适的位置上在一些场合,通过移动声明到合适的位置所带来的性能提升应该引起我们足够的重视。例如:boolis_C_Needed();voiduse(){Ccl;if(is_C_Needed()==false){return;//clwasnotneeded}//usec1herereturn;}上面这段代码中对象c1即使在有可能不使用它的情况下也会被创建,这样我们就会为它付出不必要的花费,有可能你会说一个对象c1能浪费多少时间,但是如果是这种情况呢:Cc1[1000];我想就不是说浪费就浪费了。但是我们可以通过移动声明c1的位置来改变这种情况:voiduse(){if(is_C_Needed()==false){return;//c1wasnotneeded}Cc1;//movedfromtheblock'sbeginning//usec1herereturn;}怎么样,程序的性能是不是已经得到很大的改善了呢?因此请仔细分析你的代码,把声明放在合适的位置上,它所带来的好处是你难以想象的。初始化列表我们都知道,初始化列表一般是用来初始化const或者reference数据成员。但是由于他自身的性质,我们可以通过使用初始化列表来实现性能的提升。我们先来看一段程序:classPerson{private:Cc_1;Cc_2;public:Person(constC&c1,constC&c2):c_1(c1),c_2(c2){}};当然构造函数我们也可以这样写:Person::Person(constC&c1,constC&c2){c_1=c1;c_2=c2;}那么究竟二者会带来什么样的性能差异呢,要想搞清楚这个问题,我们首先要搞清楚二者是如何执行的,先来看初始化列表:数据成员的声明操作都是在构造函数执行之前就完成了,在构造函数中往往完成的只是赋值操作,然而初始化列表直接是在数据成员声明的时候就进行了初始化,因此它只执行了一次copyconstructor。再来看在构造函数中赋值的情况:首先,在构造函数执行前会通过defaultconstructor创建数据成员,然后在构造函数中通过operator=进行赋值。因此它就比初始化列表多进行了一次函数调用。性能差异就出来了。但是请注意,如果你的数据成员都是基本类型的话,那么为了程序的可读性就不要使用初始化列表了,因为编译器对两者产生的汇编代码是相同的。(4)postfixVSprefix运算符prefix运算符++和一比它的postfix版本效率更高,因为当postfix运算符被使用的时候,会需要一个临时对象来保存改变以前的值。对于基本类型,编译器会消除这一份额外的拷贝,但是对于用户定义类型,这似乎是不可能的。因此请你尽可能使用prefix运算符。内联函数内联函数既能够去除函数调用所带来的效率负担又能够保留一般函数的优点。然而,内联函数并不是万能药,在一些情况下,它甚至能够降低程序的性能。因此使用的时候应该慎重。我们先来看看内联函数给我们带来的好处:从一个用户的角度来看,内联函数看起来和普通函数一样,它可以有参数和返回值,也可以有自己的作用域,然而它却不会引入一般函数调用所带来的负担。另外,它可以比宏更安全更容易调试。当然有一点应该意识到,inlinespecifier仅仅是对编译器的建议,编译器有权利忽略这个建议。那么编译器是如何决定函数内联与否呢?一般情况下关键性因素包括函数体的大小,是否有局部对象被声明,函数的复杂性等等。那么如果一个函数被声明为inline但是却没有被内联将会发生什么呢?理论上,当编译器拒绝内联一个函数的时候,那个函数会像普通函数一样被对待,但是还会出现一些其他的问题。例如下面这段代码://filenameTime.h#include<ctime#include<iostreamusingnamespacestd;classTime{public:inlinevoidShow(){for(inti=0;i<10;i++)cout<<time(0)<<endl;}};因为成员函数Time::Show()包括一个局部变量和一个for循环,所以编译器一般拒绝inline,并且把它当作一个普通的成员函数。但是这个包含类声明的头文件会被单独的#include进各个独立的编译单元中://filenamef1.cpp#include"Time.hj"voidf1(){Timet1;t1.Show();}//filenamef2.cpp#include"Time.h"voidf2(){Timet2;t2.Show();}结果编译器为这个程序生成了两个相同成员函数的拷贝:voidf1();voidf2();intmain(){f1();f2();return0;}当程序被链接的时候,linker将会面对两个相同的Time::Show()拷贝,于是函数重定义的连接错误发生。但是老一些的C++实现对付这种情况的办法是通过把一个un-inlined函数当作static来处理。因此每一份函数拷贝仅仅在自己的编译单元中可见,这样链接错误就解决了,但是在程序中却会留下多份函数拷贝。在这种情况下,程序的性能不但没有提升,反而增加了编译和链接时间以及最终可执行体的大小。但是幸运的是,新的C++标准中关于un-inlined函数的说法已经改变。一个符合标准C++实现应该只生成一份函数拷贝。然而,要想所有的编译器都支持这一点。可能还需要很长时间。另外关于内联函数还有两个更令人头疼的问题。第一个问题是该如何进行维护。一个函数开始的时候可能以内联的形式出现,但是随着系统的扩展,函数体可能要求添加额外的功能,结果内联函数就变得不太可能,因此需要把inlinespecifier去除以及把函数体放到一个单独的源文件中。另一个问题是当内联函数被应用在代码库的时候产生。当内联函数改变的时候,用户必须重新编译他们的代码以反映这种改变。然而对于一个非内联函数,用户仅仅需要重新链接就可以了。这里想要说的是,内联函数并不是一个增强性能的灵丹妙药。只有当函数非常短小的时候它才能得到我们想要的效果,但是如果函数并不是很短而且在很多地方都被调用的话,那么将会使得可执行体的体积增大。最令人烦恼的还是当编译器拒绝内联的时候。在老的实现中,结果很不尽人意,虽然在新的实现中有很大的改善,但是仍然还是不那么完善的。一些编译器能够足够的聪明来指出哪些函数可以内联,哪些不能,但是,大多数编译器就不那么聪明了,因此这就需要我们的经验来判断。如果内联函数不能增强行能,就避免使用它!优化你的内存使用通常优化都有几个方面:更快的运行速度,有效的系统资源使用,更小的内存使用。一般情况下,代码优化都是试图在以上各个方面进行改善。重新放置声明技术被证明是消除多余对象的建立和销毁,这样既减小了程序的大小又加快了运行速度。然而其他的优化技术都是基于一个方面------更快的速度或者是更小的内存使用。有时,这些目标是互斥的,压缩了内存的使用往往却减慢了代码速度,快速的代码却又需要更多的内存支持。下面总结两种在内存使用上的优化方法:1.BitFields在C/C++中都可以存取和访问数据的最小组成单元:bit。因为bit并不是C/C++基本的存取单元,所以这里是通过牺牲运行速度来减少内存和辅助存储器的空间的使用。注意:一些硬件结构可能提供了特殊的处理器指令来存取bit,因此bitfields是否影响程序的速度取决于具体平台。在我们的现实生活中,一个数据的许多位都被浪费了,因为某些应用根本就不会有那么大的数据范围。也许你会说,bit是如此之小,通过它就能减小存储空间的使用吗?的确,在数据量很小的情况下不会看出什么效果,但是在数据量惊人的情况下,它所节省的空间还是能够让我们的眼睛为之一亮的。也许你又会说,现在内存和硬盘越来越便宜,何苦要费半天劲,这省不了几个钱。但是还有另外一个原因一定会使你信服,那就是数字信息传输。一个分布式数据库都会在不同的地点有多份拷贝。那么数百万的纪录传输就会显得十分昂贵。Ok,现在我们就来看看该如何做吧,首先看下面这段代码:structBillingRec{longcust_id;longtimestamp;enumCallType{toll_free,local,regional,long_distance,international,cellular}type;enumCallTariff{off_peak,medium_rate,peak_time}tariff;};上面这个结构体在32位的机器上将会占用16字节,你会发现其中有许多位都被浪费了,尤其是那两个enum型,浪费更是严重,所以请看下面做出的改进:structBillingRec{intcust_id:24;//23bits+1signbitinttimestamp:24;enumCallType{//...};enumCallTariff{//...};unsignedcall:3;unsignedtariff:2;};现在一个数据从16字节缩减到了8字节,减少了一半,怎么样,效果还是显著的吧:)2.UnionsUnions通过把两个或更多的数据成员放置在相同地址的内存中来减少内存浪费,这就要求在任何时间只能有一个数据成员有效。Union可以有成员函数,包括构造函数和析构函数,但是它不能有虚函数。C++支持anonymousunionsoanonymousunion是--个未命名类型的未命名对象。例如:union{longn;void*p};//anonymousn=1000L;//membersaredirectlyaccessedp=0;//nisnowalso0不像命名的union,它不能有成员函数以及非public的数据成员。那么unions什么时候是有用的呢?下面这个类从数据库中获取一个人的信息。关键字既可以是一个特有的ID或者人名,但是二者却不能同时有效:classPersonalDetails{private:char*name;longID;//...public:PersonalDetails(constchar*nm);//keyisoftypechar*usedPersonalDetails(longid):ID(id){}//numerickeyused};上面这段代码中就会造成内存的浪费,因为在一个时间只能有一个关键字有效。anonymousunion可以在这里使用来减少内存的使用,例如:classPersonalDetailsprivate:union//anonymous{char*name;longID;};public:PersonalDetails(constchar*nm);PersonalDetails(longid):ID(id){/**/}//directaccesstoamember//...};通过使用union,PersonalDetails类的大小被减半。但是这里要说明的是,节省4个字节内存并不值得引入union所带来的麻烦,除非这个类作为数百万数据库记录的类型或者纪录在一条很慢的通信线路传输。值得注意的是unions并不引入任何运行期负担,所以这里不会有什么速度上的损失。anonymousunion的优点是它的成员可以被直接访问。速度优化在一些对速度要求非常苛刻的应用系统中,每一个CPU周期都是要争取的。这个部分展现了一些简单方法来进行速度优化。使用类来包裹长的参数列表一个函数调用的负担将会随着参数列表的增长而增加。运行时系统不得不建立堆栈来存储参数值;通常,当参数很多的时候,这样一个操作就会花费很长的时间。把参数列表包裹进一个单独的类中并且通过引用进行传递,这样将会节省很多的时间。当然,如果函数本身就很长,那么建立堆栈的时间就可以忽略了,因此也就没有必要这样做。然而,对于那些执行时间很短而且经常被调用的函数来说,包裹一个长的参数列表在对象中并且通过引用传递将会提高性能。寄存器变量registerspecifier被用来告诉编译器一个对象将被会非常多的使用,可以把它放入寄存器中。例如:voidf(){int*p=newint[3000000];registerint*p2=p;//storetheaddressinaregisterfor(registerintj=0;j<3000000;j++){*p2++=0;}//...usepdelete[]p;}循环计数是应用寄存器变量的最好的候选者。当它们没有被存入一个寄存器中,大部分的循环时间都被用在了从内存中取出变量和给变量赋新值上。如果把它存入一个寄存器中的话,将会大大减少这种负担。需要注意的是,registerspecifier仅仅是对编译器的一个建议。就好比内联函数一样,编译器可以拒绝把一个对象存储到寄存器中。另外,现代的编译器都会通过把变量放入寄存器中来优化循环计数。Registerstoragespecifier并不仅仅局限在基本类型上,它能够被应用于任何类型的对象。如果对象太大而不能装进寄存器的话,编译器仍然能够把它放入一个高速存储器中,例如cache。用registerstoragespecifier声明函数型参将会是建议编译器把实参存入寄存器中而不是堆栈中。例如:voidf(registerintj,registerDated);把那些保持不变的对象声明为const通过把对象声明为const,编译器就可以利用这个声明把这样一个对象放入寄存器中。4.Virtualfunction的运行期负担当调用一个virtualfunction,如果编译器能够解决调用的静态化,将不会引入额外的负担。另外,一个非常短的虚函数可以被内联处理。在下面这个例子中,一个聪明的编译器能够做到静态调用虚函数:#include<iostreamusingnamespacestd;classV{public:virtualvoidshow()const{cout<<"I'mV"<<endl;}};classW:publicV{public:voidshow()const{cout<<"I'mW"<<endl;}};voidf(V&v,V*pV){v.show();pV-show();}voidg()Vv;f(v,&v);}intmain(){g();return0;}如果整个程序出现在一个单独的编译单元中,编译器能够对main()中的g()进行内联替换。并且在g()中f()的调用也能够被内联处理。因为传给f()的参数的动态类型能够在编译期被知晓,因此编译器能够把对虚函数的调用静态化。但是不能保证每个编译器都这样做。然而,一些编译器确实能够利用在编译期获得参数的动态类型从而使得函数的调用在编译期间就确定了下来,避免了动态绑定的负担。5.FunctionobjectsVSfunctionpointers用functionobjects取代functionpointers的好处不
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 《保护环境习作》课件
- 《证券定价模型》课件
- 《解表药辛温解表药》课件
- 2025年雅安b2货运上岗证模拟考试
- 2025年驻马店货运从业资格证考试模拟
- 《信息管理系统概述》课件
- 2025年兰州货运从业资格考试技巧
- 创业管理课件新企业的持续发展
- 2025年防城港货物从业资格证考试
- 2025短期租车合同范本
- C语言(2023春)学习通超星期末考试答案章节答案2024年
- 《信息技术改变学习》学历案
- 自行车被盗案汇报课件
- 【广西北部湾经济区产业结构存在的问题及优化建议探析(论文)8800字】
- 2024年甘肃陇南市高层次人才引进474人历年高频难、易错点500题模拟试题附带答案详解
- 学生癫痫免责协议书
- 气瓶充装程序文件符合TSG07-2019许可规则
- 工业自动化设备维护保养指南
- 心理、行为与文化学习通超星期末考试答案章节答案2024年
- 2024事业单位办公室的年度工作总结
- 第2课 新航路开辟后食物物种交流 教学设计-2023-2024学年高中历史统编版2019选择性必修2
评论
0/150
提交评论