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

下载本文档

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

文档简介

C++代码优化经验总结优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法的效率,况且我也没有这个能力。我只是想把一些能够简单的应用到你的C++代码中的优化技术总结在这里,这样,当你遇到几种不一致的编程策略的时候,就能够对每种策略的性能进行一个大概的估计。这也是本文的目的之所在.目录:一.优化之前二.声明的放置三.内联函数.优化你的内存使用.速度优化.最后的求助一.优化之前在进行优化之前,我们首先应该做的是发现我们代码的瓶颈(bottleneck)在哪里。.我们先来看看内联函数给我们带来的好处:从一个用户的角度来看,内联函数看起来与普通函数一样,它能够有参数与返回值,也能够有自己的作用域,然而它却不可能引入通常函数调用所带来的负担。另外,它能够比宏更安全更容易调试。当然有一点应该意识到,inlinespecifier仅仅是对编译器的建议,编译器有权利忽略这个建议。那么编译器是如何决定函数内联与否呢?通常情况下关键性因素包含函数体的大小,是否有局部对象被声明,函数的复杂性等等。.那么假如一个函数被声明为inline但是却没有被内联将会发生什么呢?理论上,当编译器拒绝内联一个函数的时候,那个函数会像普通函数一样被对待,但是还会出现一些其他的问题。比如下面这段代码://Time,h#include<ctime>#include<iostream>usingnamespacestd;classTime(public:inlinevoidShow(){for(inti=0;i<10;i++)cout<<time(0)<<endl;});由于成员函数Time:Show()包含一个局部变量与一个for循环,因此编译器通常拒绝inline,同时把它当作一个普通的成员函数。但是这个包含类声明的头文件会被单独的#include进各个独立的编译单元中://fl.cpp1i•"11〃T*1•〃ffmclude1ime.njvoidfl()Timetl;tl.Show();)//f2.cppttinclude"Time.h〃voidf2()(Timet2;t2.Show();)结果编译器为这个程序生成了两个相同成员函数的拷贝:voidfl();voidf2();intmain()fl();f2();return0;)当程序被链接的时候,linker将会面对两个相同的Time::Show。拷贝,因此函数重定义的连接错误发生。但是老一些的C++实现应付这种情况的办法是通过把一个un-inlined函数当作static来处理。因此每一份函数拷贝仅仅在自己的编译单元中可见,这样链接错误就解决了,但是在程序中却会留下多份函数拷贝。在这种情况下,程序的性能不但没有提升,反而增加了编译与链接时间与最终可执行体的大小。但是幸运的是,新的C++标准中关于un-inlined函数的说法已经改变。一个符合标准C++实现应该只生成一份函数拷贝。然而,要想所有的编译器都支持这一点可能还需要很长时间。另外关于内联函数还有两个更令人头疼的问题。第一个问题是该如何进行保护。一个函数开始的时候可能以内联的形式出现,但是随着系统的扩展,函数体可能要求添加额外的功能,结果内联函数就变得不太可能,因此需要把inlinespecifier去除与把函数体放到一个单独的源文件中。另一个问题是当内联函数被应用在代码库的时候产生。当内联函数改变的时候,用户务必重新编译他们的代码以反映这种改变。然而关于一个非内联函数,用户仅仅需要重新链接就能够了。这里想要说的是,内联函数并不是一个增强性能的灵丹妙药。只有当函数非常短小的时候它才能得到我们想要的效果,但是假如函数并不是很短而且在很多地方都被调用的话,那么将会使得可执行体的体积增大。最令人烦恼的还是当编译器拒绝内联的时候。在老的实现中,结果很不尽人意,尽管在新的实现中有很大的改善,但是仍然还是不那么完善的。一些编译器能够足够的聪明来指出什么函数能够内联什么不能,但是,大多数编译器就不那么聪明了,因此这就需要我们的经验来推断。假如内联函数不能增强行能,就避免使用它!四.优化你的内存使用通常优化都有几个方面:更快的运行速度,有效的系统资源使用,更小的内存使用。通常情况下,代码优化都是试图在以上各个方面进行改善。重新放置声明技术被证明是消除多余对象的建立与销毁,这样既减小了程序的大小又加快了运行速度。然而其他的优化技术都是基于一个方面更快的速度或者者是更小的内存使用。有的时候,这些目标是互斥的,压缩了内存的使用往往却减慢了代码速度,快速的代码却又需要更多的内存支持。下面总结两种在内存使用上的优化方法:BitFields在C/C++中都能够存取与访问数据的最小构成单元:bito由于bit并不是C/C++基本的存取单元,因此这里是通过牺牲运行速度来减少内存与辅助存储器的空间的使用。注意:一些硬件结构可能提供了特殊的处理器指令来存取bit,因此bitfields是否影响程序的速度取决于具体平台。在我们的现实生活中,一个数据的许多位都被浪费了,由于某些应用根本就不可能有那么大的数据范围。也许你会说,bit是如此之小,通过它就能减小存储空间的使用吗?的确,在数据量很小的情况下不可能看出什么效果,但是在数据量惊人的情况下,它所节约的空间还是能够让我们的眼睛为之一亮的。也许你又会说,现在内存与硬盘越来越便宜,何苦要费半天劲,这省不了几个钱。但是还有另外一个原因一定会使你信服,那就是数字信息传输。一个分布式数据库都会在不一致的地点有多份拷贝。那么数百万的纪录传输就会显得十分昂贵。Ok,现在我们就来看看该如何做吧,首先看下面这段代码:structBillingReclongcust_id;longtimestamp;enumCallTypetoll_free,local,regional,long_distance,international,cellular}type;enumCallTariffoff_peak,medium_rate,peak_time}tariff;);上面这个结构体在32位的机器上将会占用16字节,你会发现其中有许多位都被浪费了,尤其是那两个enum型,浪费更是严重,因此请看下面做出的改进:structBillingRec(intcustid:24;//23bits+1signbitinttimestamp:24;enumCallType{//...);enumCallTariff{//...然而当你做这件情况的时候切忌从一个debug-version进行推断,由于debug-version中包含了许多额外的代码。一个debug-version可执行体要比release-version大出40%。那些额外的代码都是用来支持调试的,比如说符号的查找。大多数实现都为debug-version与release-version提供了不一致的operatornew与库函数。而且,一个release-version的执彳亍体可能已经通过多种途径进行了优化,包含不必要的临时对象的消除,循环展开,把对象移入寄存器,内联等等。另外,我们要把调试与优化区分开来,它们是在完成不一致的任务。debug-version是用来追捕bugs与检查程序是否有逻辑上的问题。release-version则是用来做一些性能上的调整与进行优化。下面就让我们来看看有什么代码优化技术吧!unsignedcall:3;unsignedtariff:2;);现在一个数据从16字节缩减到了8字节,减少了一半,怎么样,效果还是显著的吧:)UnionsUnions通过把两个或者更多的数据成员放置在相同地址的内存中来减少内存浪费,这就要求在任何时间只能有一个数据成员有效。Union能够有成员函数,包含构造函数与析构函数,但是它不能有虚函数。C++支持anonymousunionsoanonymousunion是一个未命名类型的未命名对象。比如:union{longn;void*p};//anonymousn二lOOOL;//membersaredirectlyaccessedp=0;//nisnowalso0不像命名的union,它不能有成员函数与非public的数据成员。那么unions什么时候是有用的呢?下面这个类从数据库中获取一个人的信息。关键字既可以是一个特有的ID或者者人名,但是二者却不能同时有效:classPersonalDetails{private:char*name;longID;//...public:PersonalDetails(constchar*nm);//keyisoftypechar*usedPersonalDetails(longid):ID(id){}//numerickeyused上面这段代码中就会造成内存的浪费,由于在一个时间只能有一个关键字有效。anonymousunion能够在这里使用来减少内存的使用,比如:classPersonalDetails{private:union//anonymous{char*name;longID;};public:PersonalDetails(constchar*nm);PersonalDetails(longid):ID(id){/**/}//directaccesstoamember};通过使用union,PersonalDetails类的大小被减半。但是这里要说明的是,节约4个字节内存并不值得引入union所带来的烦恼,除非这个类作为数百万数据库记录的类型或者者纪录在一条很慢的通信线路传输。值得注意的是unions并不引入任何运行期负担,因此这里不会有什么速度上的缺失。anonymousunion的优点就是它的成员能够被直接访问。五.速度优化在一些对速度要求非常苛刻的应用系统中,每一个CPU周期都是要争取的。这个部分展现了一些简单方法来进行速度优化。.使用类来包裹长的参数列表一个函数调用的负担将会随着参数列表的增长而增加。运行时系统不得不建立堆栈来存储参数值;通常,当参数很多的时候,这样一个操作就会花费很长的时间。把参数列表包裹进一个单独的类中同时通过引用进行传递,这样将会节约很多的时间O当然,假如函数本身就很长,那么建立堆栈的时间就能够忽略了,因此也就没有必要这样做。然而,关于那些执行时间很短而且经常被调用的函数来说,包裹一个长的参数列表在对象中同时通过引用传递将会提高性能。.寄存器变量registerspecifier被用来告诉编译器一个对象将被会非常多的使用,能够把它放入寄存器中。比如:voidf()int*p=newint[3000000];registerint*p2=p;//storetheaddressinaregisterfor(registerintj=0;j<3000000;j++)(*p2++=0;)//...usepdelete[]p;)循环计数是应用寄存器变量的最好的候选者。当它们没有被存入一个寄存器中,大部分的循环时间都被用在了从内存中取出变量与给变量赋新值上。假如把它存入一个寄存器中的话,将会大大减少这种负担。需要注意的是,registerspecifier仅仅是对编译器的一个建议。就好比内联函数一样,编译器能够拒绝把一个对象存储到寄存器中。另外,现代的编译器都会通过把变量放入寄存器中来优化循环计数。Registerstoragespecifier并不仅仅局限在基本类型上,它能够被应用于任何类型的对象。假如对象太大而不能装进寄存器的话,编译器仍然能够把它放入一个高速存储器中,比如cacheo用registerstoragespecifier声明函数型参将会是建议编译器把实参存入寄存器中而不是堆栈中。比如:voidf(registerintj,registerDated);.把那些保持不变的对象声明为const通过把对象声明为const,编译器就能够利用这个声明把这样一个对象放入寄存器中。.Virtualfunction的运行期负担当调用一个virtualfunction,假如编译器能够解决调用的静态化,将不可能引入额外的负担。另外,一个非常短的虚函数能够被内联处理。在下面这个例子中,一个聪明的编译器能够做到静态调用虚函数:ttinclude<iostream>usingnamespacestd;classV(public:virtualvoidshow()const{cout〈〈〃I'niV〃<〈endl;}};classW:publicVpublic:voidshow()const{coutVTmWz,<<endl;);voidf(V&v,V*pV)(v.show();pV->show();)voidg()(Vv;f(V,&v);)intmain()g();return0;假如整个程序出现在一个单独的编译单元中,编译器能够对main()中的g()进行内联替换。同时在g()中f()的调用也能够被内联处理。由于传给f()的参数的动态类型能够在编译期被知晓,因此编译器能够把对虚函数的调用静态化。但是不能保证每个编译器都这样做O然而,一些编译器确实能够利用在编译期获得参数的动态类型从而使得函数的调用在编译期间就确定了下来,避免了动态绑定的负担。5.FunctionobjectsVSfunctionpointers用functionobjects取代functionpointers的好处不仅仅局限在能够泛化与简单的保护性上。而且编译器能够对functionobject的函数调用进行内联处理,从而进一步的增强了性二.声明的放置程序中变量与对象的声明放在什么位置将会对性能产生显著影响。同样,对postfix与prefix运算符的选择也会影响性能。这一部分我们集中讨论四个问题:初始化v.s赋值,在程序确实要使用的地方放置声明,构造函数的初始化列表,prefixv.spostfix运算符(1)请使用初始化而不是赋值在C语言中只同意在一个函数体的开头进行变量的声明,然而在C++中声明能够出现在程序的任何位置。这样做的目的是希望把对象的声明拖延到确实要使用它的时候再进行。这样做能够有两个好处:1.确保了对象在它被使用前不可能被程序的其他部分恶意修改。如果对象在开头就被声明然而却在20行以后才被使用的话,就不能做这样的保证。2.使我们能六.最后的求助迄今为止为大家展示的优化技术并没有在设计与代码的可读性上做出妥协。事实上,它们中的一些还提高了软件的稳固性与可保护性。但是在一些对时间与内存有严格限制的软件开发中,上面的技术可能还不够;有可能还需要一些会影响软件的可移植性与扩展性的技术。但是这些技术只能在所有其他的优化技术都被应用但是还不符合要求的情况下使用。.关闭RTTI与特殊处理支持当你导入纯C代码给C++编译器的时候,你可能会发现有一些性能上的缺失。这并不是语言或者者编译器的错误,而是编译器作出的一些调整。假如你想获得与C编译器同样的性能,那么请关闭编译器对RTTI与特殊处理的支持。为什么会这样呢?由于为了支持RTTI与特殊处理,C++编译器会插入额外的代码。这样就增加了可执行体的大小,从而使得效率有所下降。当应用纯C代码的时候,那些额外的代码是不需要的,因此你能够通过关闭来避免它。.内联汇编对时间要求苛刻的部分能够用本地汇编来重写。结果可能是速度上的显著提高。然而,这个方法不能想当然的就去实施,由于它将使得将来的修改非常的困难。保护代码的程序员可能对汇编并不熟悉。假如想要把软件运行于其他平台也需要重写汇编代码部分。另外,开发与测试汇编代码是一件辛苦的工作,它将花费更长的时间。.直接与操作系统进行交互API函数能够使你直接与操作系统进行交互。有的时候,直接执行一个系统命令可能会快许多。出于这个目的,你能够使用标准函数system。。比如,在一个dos/windows系统下,你能够这样显示当前目录下的文件:ttinclude<cstdlib>usingnamespacestd;intmain()(system(〃dir〃);//executethe〃dir〃command)注意:这里是在速度与可移植性与可扩展性之间做出的折衷希望二频的编程爱好者跟帖,谈谈自己的见解。tarsen转帖自雁塔晨钟bbs有机会通过用初始化取代赋值来达到性能的提升,从前声明只能放在开头,然而往往开始的时候我们还没有获得我们想要的值,因此初始化所带来的好处就无法被应用。但是现在我们能够在我们获得了想要的值的时候直接进行初始化,从而省去了一步。注意,或者许对于基本类型来说,初始化与赋值之间可能不可能有什么差异,但是关于用户定义的类型来说,二者就会带来显著的不一致,由于赋值会多进行一次函数调用operator二。因止匕当我们在赋值与初始化之间进行选择的话,初始化应该是我们的首选。(2)把声明放在合适的位置上在一些场合,通过移动声明到合适的位置所带来的性能提升应该引起我们足够的重视o比如:boolis_C_Needed();voiduse()Ccl;if(is_C_Needed()==false)(return;//clwasnotneeded}//useclherereturn;}上面这段代码中对象Cl即使在有可能不使用它的情况下也会被创建,这样我们就会为它付出不必要的花费,有可能你会说一个对象C1能浪费多少时间,但是假如是这种情况呢:Ccl[1000];我想就不是说浪费就浪费了。但是我们能够通过移动声明cl的位置来改变这种情况:voiduse()(if(is_C_Needed()二二false)(return;//clwasnotneeded}Ccl;//movedfromtheblock'sbeginning//useclherereturn;)怎么样,程序的性能是不是已经得到很大的改善了呢?因此请认真分析你的代

温馨提示

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

评论

0/150

提交评论