《C++代码优化经验总结.docx》由会员分享,可在线阅读,更多相关《C++代码优化经验总结.docx(26页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、C+代代码优化化经验总总结优化是一一个非常常大的主主题,本本文并不不是去深深入探讨讨性能分分析理论论,算法法的效率率,况且且我也没没有这个个能力。我只是是想把一一些可以以简单的的应用到到你的CC+代代码中的的优化技技术总结结在这里里,这样样,当你你遇到几几种不同同的编程程策略的的时候,就就可以对对每种策策略的性性能进行行一个大大概的估估计。这这也是本本文的目目的之所所在. 目录: 一. 优化化之前二. 声明明的放置置 三. 内联联函数 四. 优化化你的内内存使用用 五. 速度度优化 六. 最后后的求助助 一. 优优化之前前 在进进行优化化之前,我我们首先先应该做做的是发发现我们们代码的的瓶颈(
2、bbotttlenneckk)在哪哪里。 然而当你你做这件件事情的的时候切切忌从一一个deebugg-veersiion进进行推断断,因为为debbug-verrsioon中包包 含了许多多额外的的代码。一个ddebuug-vverssionn可执行行体要比比relleasse-vverssionn大出440%。那些额额 外的代码码都是用用来支持持调试的的,比如如说符号号的查找找。大多多数实现现都为ddebuug-vverssionn和reele ase-verrsioon提供供了不同同的opperaatorr neew以及及库函数数。而且且,一个个relleasse-vverssionn的执
3、行行 体可能已已经通过过多种途途径进行行了优化化,包括括不必要要的临时时对象的的消除,循循环展开开,把对对象 移入寄存存器,内内联等等等。 另外外,我们们要把调调试和优优化区分分开来,它它们是在在完成不不同的任任务。 debbug-verrsioon 是是 用来追捕捕buggs以及及检查程程序是否否有逻辑辑上的问问题。rreleeasee-veersiion则则是用来来做一些些性能上上 的调整以以及进行行优化。 下面就让让我们来来看看有有哪些代代码优化化技术吧吧! 二. 声声明的放放置 程序序中变量量和对象象的声明明放在什什么位置置将会对对性能产产生显著著影响。同样,对对posstfiix和
4、preffix运运算符的的选择也也会影响响性能。这一部部分我们们集中讨讨论四个个问题:初始化化v.ss 赋值值, 在程序确确实要使使用的地地方放置置声明,构构造函数数的初始始化列表表,prrefiix vv.s posstfiix运算算符 。 (1) 请使用用初始化化而不是是赋值 在CC语言中中只允许许在一个个函数体体的开头头进行变变量的声声明,然然而在CC+中中声明可可以出现现在 程序的任任何位置置。这样样做的目目的是希希望把对对象的声声明拖延延到确实实要使用用它的时时候再进进行。 这样做可可以有两两个好处处:1. 确保保了对象象在它被被使用前前不会被被程序的的其他部部分恶意意修改。如 果对
5、象在在开头就就被声明明然而却却在200行以后后才被使使用的话话,就不不能做这这样的保保证。22. 使使我们 有机会通通过用初初始化取取代赋值值来达到到性能的的提升,从从前声明明只能放放在开头头,然而而往往开开始 的时候我我们还没没有获得得我们想想要的值值,因此此初始化化所带来来的好处处就无法法被应用用。但是是现在 我们可以以在我们们获得了了想要的的值的时时候直接接进行初初始化,从从而省去去了一步步。注意意,或许许对 于基本类类型来说说,初始始化和赋赋值之间间可能不不会有什什么差异异,但是是对于用用户定义义的类型型来说 ,二者就就会带来来显著的的不同,因因为赋值值会多进进行一次次函数调调用-oo
6、perratoor =。因此此当我 们在赋值值和初始始化之间间进行选选择的话话,初始始化应该该是我们们的首选选。 (2) 把声明明放在合合适的位位置上 在一一些场合合,通过过移动声声明到合合适的位位置所带带来的性性能提升升应该引引起我们们足够的的重视 。例如: boool is_C_NNeedded(); vooid usee() CC c11; iif (is_C_NNeedded() = ffalsse) rretuurn; /c1 wass noot nneedded /usse cc1 hheree rretuurn; 上面这段段代码中中对象cc1即使使在有可可能不使使用它的的情况下下
7、也会被被创建,这这样我们们就会为为它付 出不必要要的花费费,有可可能你会会说一个个对象cc1能浪浪费多少少时间,但但是如果果是这种种情况呢呢:C c1110000;我我想就不不是说浪浪费就浪浪费了。但是我我们可以以通过移移动声明明c1的的位置来来改变这这种情 况: vooid usee() iif (is_C_NNeedded() = ffalsse) rretuurn; /c1 wass noot nneedded CC c11; /moovedd frrom thee bllockks begginnningg /usse cc1 hheree rretuurn; 怎么样,程程序的性性能是
8、不不是已经经得到很很大的改改善了呢呢?因此此请仔细细分析你你的代码码,把声声明 放在合适适的位置置上,它它所带来来的好处处是你难难以想象象的。 (3) 初始化化列表 我们们都知道道,初始始化列表表一般是是用来初初始化cconsst或者者reffereencee数据成成员。但但是由于于 他自身的的性质,我我们可以以通过使使用初始始化列表表来实现现性能的的提升。我们先先来看一一段程序序: cllasss Peersoon prrivaate: CC c_1; CC c_2; puubliic: PPersson(connst C& c1, coonstt C& c22 ): c_1(cc1), c
9、_2(cc2) ; 当然构造造函数我我们也可可以这样样写: Peersoon:Perrsonn(coonstt C& c11, cconsst CC& cc2) cc_1 = cc1; cc_2 = cc2; 那么究竟竟二者会会带来什什么样的的性能差差异呢,要要想搞清清楚这个个问题,我我们首先先要搞清清楚二者者 是如何执执行的,先先来看初初始化列列表:数数据成员员的声明明操作都都是在构构造函数数执行之之前就完完成 了,在构构造函数数中往往往完成的的只是赋赋值操作作,然而而初始化化列表直直接是在在数据成成员声明明的时 候就进行行了初始始化,因因此它只只执行了了一次ccopyy coonsttru
10、cctorr。再来来看在构构造函数数中赋值值的 情况:首首先,在在构造函函数执行行前会通通过deefauult connstrructtor创创建数据据成员,然然后在构构造函 数中通过过opeerattor =进行行赋值。因此它它就比初初始化列列表多进进行了一一次函数数调用。性能差差异 就出来了了。但是是请注意意,如果果你的数数据成员员都是基基本类型型的话,那那么为了了程序的的可读性性就 不要使用用初始化化列表了了,因为为编译器器对两者者产生的的汇编代代码是相相同的。 (4) posstfiix VVS ppreffix 运算符符 prrefiix运算算符+和比它的的posstfiix版本本效
11、率更更高,因因为当pposttfixx运算符符被使用用的时 候,会需需要一个个临时对对象来保保存改变变以前的的值。对对于基本本类型,编编译器会会消除这这一份额额外 的拷贝,但但是对于于用户定定义类型型,这似似乎是不不可能的的。因此此请你尽尽可能使使用prrefiix运算算符 。 三. 内内联函数数 内联联函数既既能够去去除函数数调用所所带来的的效率负负担又能能够保留留一般函函数的优优点。然然而, 内联函数数并不是是万能药药,在一一些情况况下,它它甚至能能够降低低程序的的性能。因此在在使用的的时候 应该慎重重。 1我们们先来看看看内联联函数给给我们带带来的好好处:从从一个用用户的角角度来看看,内
12、联联函数看看起来和和 普通函数数一样,它它可以有有参数和和返回值值,也可可以有自自己的作作用域,然然而它却却不会引引入一般般 函数调用用所带来来的负担担。另外外,它可可以比宏宏更安全全更容易易调试。 当然然有一点点应该意意识到,iinliine speeciffierr仅仅是是对编译译器的建建议,编编译器有有权利忽忽 略这个建建议。那那么编译译器是如如何决定定函数内内联与否否呢?一一般情况况下关键键性因素素包括函函数体 的大小,是是否有局局部对象象被声明明,函数数的复杂杂性等等等。 2那么么如果一一个函数数被声明明为innlinne但是是却没有有被内联联将会发发生什么么呢?理理论上,当当编译
13、器拒绝内内联一个个函数的的时候,那那个函数数会像普普通函数数一样被被对待,但但是还会会出现一一些其他他 的问题。例如下下面这段段代码: / TTimee.h #iinclludee #iinclludee ussingg naamesspacce sstd; cllasss Tiime puubliic: iinliine voiid SShoww() ffor (innt ii = 0; i110; i+) ccouttttimee(0)eendll; ; 因为成员员函数TTimee:SShoww()包包括一个个局部变变量和一一个foor循环环,所以以编译器器一般拒拒绝innlinne ,并
14、且把把它当作作一个普普通的成成员函数数。但是是这个包包含类声声明的头头文件会会被单独独的#iinclludee 进各个独独立的编编译单元元中: / ff1.ccpp #iinclludee TTimee.hjj vooid f1() TTimee t11; tt1.SShoww(); / ff2.ccpp #iinclludee TTimee.h vooid f2() TTimee t22; tt2.SShoww(); 结果编译译器为这这个程序序生成了了两个相相同成员员函数的的拷贝: vooid f1(); vooid f2(); innt mmainn() ff1(); ff2(); rre
15、tuurn 0; 当程程序被链链接的时时候,llinkker将将会面对对两个相相同的TTimee:SShoww()拷拷贝,于于是函数数重定义的连接接错误发发生。但但是老一一些的CC+实实现对付付这种情情况的办办法是通通过把一一个unn-innlinned函函 数当作sstattic来来处理。因此每每一份函函数拷贝贝仅仅在在自己的的编译单单元中可可见,这这样链接接错误 就解决了了,但是是在程序序中却会会留下多多份函数数拷贝。在这种种情况下下,程序序的性能能不但没没有提 升,反而而增加了了编译和和链接时时间以及及最终可可执行体体的大小小。 但是是幸运的的是,新新的C+标准准中关于于un-inlli
16、need函数数的说法法已经改改变。一一个符合合标准CC+ +实现应应该只生生成一份份函数拷拷贝。然然而,要要想所有有的编译译器都支支持这一一点可能能还需要要很长时时 间。另外外关于内内联函数数还有两两个更令令人头疼疼的问题题。第一一个问题题是该如如何进行行维护。一个 函数开始始的时候候可能以以内联的的形式出出现,但但是随着着系统的的扩展,函函数体可可能要求求添加额额外 的功能,结结果内联联函数就就变得不不太可能能,因此此需要把把inllinee sppeciifieer去除除以及把把函数体体 放到一个个单独的的源文件件中。另另一个问问题是当当内联函函数被应应用在代代码库的的时候产产生。当当内联
17、 函数改变变的时候候,用户户必须重重新编译译他们的的代码以以反映这这种改变变。然而而对于一一个非内内联函 数,用户户仅仅需需要重新新链接就就可以了了。 这里里想要说说的是,内内联函数数并不是是一个增增强性能能的灵丹丹妙药。只有当当函数非非常短小小的 时候它才才能得到到我们想想要的效效果,但但是如果果函数并并不是很很短而且且在很多多地方都都被调用用的话 ,那么将将会使得得可执行行体的体体积增大大。最令令人烦恼恼的还是是当编译译器拒绝绝内联的的时候。在老 的实现中中,结果果很不尽尽人意,虽虽然在新新的实现现中有很很大的改改善,但但是仍然然还是不不那么完完善 的。一些些编译器器能够足足够的聪聪明来指
18、指出哪些些函数可可以内联联哪些不不能,但但是,大大多数编编译器 就不那么么聪明了了,因此此这就需需要我们们的经验验来判断断。如果果内联函函数不能能增强行行能,就就避免 使用它! 四. 优优化你的的内存使使用 通常常优化都都有几个个方面:更快的的运行速速度,有有效的系系统资源源使用,更更小的内内存使用用。 一般情况况下,代代码优化化都是试试图在以以上各个个方面进进行改善善。重新新放置声声明技术术被证明明是消 除多余对对象的建建立和销销毁,这这样既减减小了程程序的大大小又加加快了运运行速度度。然而而其他的的优化 技术都是是基于一一个方面面-更快的的速度或或者是更更小的内内存使用用。有时时,这些些目
19、标是是互斥 的,压缩缩了内存存的使用用往往却却减慢了了代码速速度,快快速的代代码却又又需要更更多的内内存支持持。下 面总结两两种在内内存使用用上的优优化方法法: 1 BBit Fieeldss 在CC/C+中都都可以存存取和访访问数据据的最小小组成单单元:bbit。因为bbit并并不是CC/C+基本本的 存取单元元,所以以这里是是通过牺牺牲运行行速度来来减少内内存和辅辅助存储储器的空空间的使使用。注注意: 一些硬件件结构可可能提供供了特殊殊的处理理器指令令来存取取bitt,因此此bitt fiieldds是否否影响程程序的速速 度取决于于具体平平台。 在我我们的现现实生活活中,一一个数据据的许
20、多多位都被被浪费了了,因为为某些应应用根本本就不会会有那 么大的数数据范围围。也许许你会说说,biit是如如此之小小,通过过它就能能减小存存储空间间的使用用吗?的的确 ,在数据据量很小小的情况况下不会会看出什什么效果果,但是是在数据据量惊人人的情况况下,它它所节省省的空 间还是能能够让我我们的眼眼睛为之之一亮的的。也许许你又会会说,现现在内存存和硬盘盘越来越越便宜,何何苦 要费半天天劲,这这省不了了几个钱钱。但是是还有另另外一个个原因一一定会使使你信服服,那就就是数字字信息 传输。一一个分布布式数据据库都会会在不同同的地点点有多份份拷贝。那么数数百万的的纪录传传输就会会显得 十分昂贵贵。Okk
21、,现在在我们就就来看看看该如何何做吧,首首先看下下面这段段代码: sttrucct BBilllinggRecc llongg cuust_id; llongg tiimesstammp; eenumm CaallTTypee ttolll_frree, llocaal, rregiionaal, llongg_diistaancee, iinteernaatioonall, ccelllulaar tyype; eenumm CaallTTariiff ooff_peaak, mmediium_ratte, ppeakk_tiime taarifff; ; 上面这个个结构体体在322位的机机
22、器上将将会占用用16字字节,你你会发现现其中有有许多位位都被浪浪费了,尤尤 其是那两两个ennum型型,浪费费更是严严重,所所以请看看下面做做出的改改进: sttrucct BBilllinggRecc iint cusst_iid: 24; / 233 biits + 11 siign bitt iint timmesttampp: 224; eenumm CaallTTypee /. ; eenumm CaallTTariiff /. ; uunsiigneed ccalll: 33; uunsiigneed ttariiff: 2; ; 现在一个个数据从从16字字节缩减减到了88字节,减
23、减少了一一半,怎怎么样,效效果还是是显著的的吧:) 2 UUnioons Unnionns通过过把两个个或更多多的数据据成员放放置在相相同地址址的内存存中来减减少内存存浪费,这这就 要求在任任何时间间只能有有一个数数据成员员有效。Uniion 可以有有成员函函数,包包括构造造函数和和析构 函数,但但是它不不能有虚虚函数。C+支持aanonnymoous uniionss。annonyymouus uunioon是一一个未命命名 类型的未未命名对对象。例例如: unnionn lonng nn; vvoidd * p; / annonyymouus n = 110000L; / memmberr
24、s aare dirrecttly acccesssed p = 00; / nn iss noow aalsoo 0 不像命名名的unnionn,它不不能有成成员函数数以及非非pubblicc的数据据成员。 那么unnionns什么么时候是是有用的的呢?下下面这个个类从数数据库中中获取一一个人的的信息。关键字字既可 以是一个个特有的的ID或或者人名名,但是是二者却却不能同同时有效效: cllasss PeersoonallDettaills prrivaate: ccharr * namme; llongg IDD; /. puubliic: PPerssonaalDeetaiils(con
25、nst chaar *nm); /keey iis oof ttypee chhar * uusedd PPerssonaalDeetaiils(lonng iid) : IID(iid) /nnumeericc keey uusedd ; 上面这段段代码中中就会造造成内存存的浪费费,因为为在一个个时间只只能有一一个关键键字有效效。annonyymouus unioon可以以在这里里使用来来减少内内存的使使用,例例如: cllasss PeersoonallDettaills prrivaate: uunioon /annonyymouus ccharr * namme; llongg IDD
26、; ; puubliic: PPerssonaalDeetaiils(connst chaar *nm); PPerssonaalDeetaiils(lonng iid) : IID(iid) /*/ / diirecct aacceess to a mmembber /. ; 通过使用用uniion,PPerssonaalDeetaiils类类的大小小被减半半。但是是这里要要说明的的是,节节省4 个字节节 内存并不不值得引引入unnionn所带来来的麻烦烦,除非非这个类类作为数数百万数数据库记记录的类类型或者者纪录 在一条很很慢的通通信线路路传输。值得注注意的是是uniionss并不引引入任
27、何何运行期期负担,所所以这里里不 会有什么么速度上上的损失失。annonyymouus uunioon的优优点就是是它的成成员可以以被直接接访问。 五. 速速度优化化 在一一些对速速度要求求非常苛苛刻的应应用系统统中,每每一个CCPU周周期都是是要争取取的。这这个部分分展 现了一些些简单方方法来进进行速度度优化。 1 使使用类来来包裹长长的参数数列表 一个个函数调调用的负负担将会会随着参参数列表表的增长长而增加加。运行行时系统统不得不不建立堆堆栈来 存储参数数值;通通常,当当参数很很多的时时候,这这样一个个操作就就会花费费很长的的时间。 把参参数列表表包裹进进一个单单独的类类中并且且通过引引用
28、进行行传递,这这样将会会节省很很多的时时间 。当然,如如果函数数本身就就很长,那那么建立立堆栈的的时间就就可以忽忽略了,因因此也就就没有必必要这 样做。然然而,对对于那些些执行时时间很短短而且经经常被调调用的函函数来说说,包裹裹一个长长的参数数列表 在对象中中并且通通过引用用传递将将会提高高性能。 2 寄寄存器变变量 reegissterr sppeciifieer被用用来告诉诉编译器器一个对对象将被被会非常常多的使使用,可可以把它它放入 寄存器中中。例如如: vooid f() iint *p = nnew intt300000000; rregiisteer iint *p22 = p;
29、/sstorre tthe adddresss iin aa reegissterr ffor (reegissterr innt jj = 0; j3300000000; jj+) *p2+ = 0; /.usse pp ddeleete p; 循环环计数是是应用寄寄存器变变量的最最好的候候选者。当它们们没有被被存入一一个寄存存器中,大大部 分的循环环时间都都被用在在了从内内存中取取出变量量和给变变量赋新新值上。如果把把它存入入一个寄寄存器 中的话,将将会大大大减少这这种负担担。需要要注意的的是,rregiisteer sspeccifiier仅仅仅是对对编译器器的 一个建议议。就好好比内联
30、联函数一一样,编编译器可可以拒绝绝把一个个对象存存储到寄寄存器中中。另外外,现 代的编译译器都会会通过把把变量放放入寄存存器中来来优化循循环计数数。Reegissterr sttoraage speeciffierr 并不仅仅仅局限在在基本类类型上,它它能够被被应用于于任何类类型的对对象。如如果对象象太大而而不能装装进 寄存器的的话,编编译器仍仍然能够够把它放放入一个个高速存存储器中中,例如如cacche。 用rregiisteer sstorragee sppeciifieer声明明函数型型参将会会是建议议编译器器把实参参存入寄寄存器中中 而不是堆堆栈中。例如: vooid f(rregii
31、steer iint j, reggistter Datte dd); 3 把把那些保保持不变变的对象象声明为为connst 通过过把对象象声明为为connst,编编译器就就可以利利用这个个声明把把这样一一个对象象放入寄寄存器中中。 4 VVirttuall fuuncttionn的运行行期负担担 当调调用一个个virrtuaal ffuncctioon,如如果编译译器能够够解决调调用的静静态化,将将不会引引入额外外 的负担。另外,一一个非常常短的虚虚函数可可以被内内联处理理。在下下面这个个例子中中,一个个聪明的的编 译器能够够做到静静态调用用虚函数数: #iinclludee ussingg
32、 naamesspacce sstd; cllasss V puubliic: vvirttuall vooid shoow() coonstt couutIm VVenddl; ; cllasss W : ppubllic V puubliic: vvoidd shhow() cconsst cooutIIm Wshoow(); vooid g() VV v; ff(v, &vv); innt mmainn() gg(); rretuurn 0; 如果果整个程程序出现现在一个个单独的的编译单单元中,编编译器能能够对mmainn()中中的g()进行行内联替替 换。并且且在g()中ff()的的调
33、用也也能够被被内联处处理。因因为传给给f()的参数数的动态态类型能能够在编编译 期被知晓晓,因此此编译器器能够把把对虚函函数的调调用静态态化。但但是不能能保证每每个编译译器都这这样做 。然而,一一些编译译器确实实能够利利用在编编译期获获得参数数的动态态类型从从而使得得函数的的调用在在编 译期间就就确定了了下来,避避免了动动态绑定定的负担担。 5 FFuncctioon oobjeectss VSS fuuncttionn poointterss 用funnctiion objjectts取代代funnctiion poiinteers的的好处不不仅仅局局限在能能够泛化化和简单单的维护护性 上。
34、而且且编译器器能够对对funnctiion objjectt的函数数调用进进行内联联处理,从从而进一一步的增增强了性性 能 六. 最最后的求求助 迄今今为止为为大家展展示的优优化技术术并没有有在设计计以及代代码的可可读性上上做出妥妥协。事事实上 ,它们中中的一些些还提高高了软件件的稳固固性和可可维护性性。但是是在一些些对时间间和内存存有严格格限制 的软件开开发中,上上面的技技术可能能还不够够;有可可能还需需要一些些会影响响软件的的可移植植性和扩扩展 性的技术术。但是是这些技技术只能能在所有有其他的的优化技技术都被被应用但但是还不不符合要要求的情情况下 使用。 1 关关闭RTTTI和和异常处处理
35、支持持 当你你导入纯纯C代码码给C+编译译器的时时候,你你可能会会发现有有一些性性能上的的损失。这并不不是 语言或者者编译器器的错误误,而是是编译器器作出的的一些调调整。如如果你想想获得和和C编译译器同样样的性能能 ,那么请请关闭编编译器对对RTTTI以及及异常处处理的支支持。为为什么会会这样呢呢?因为为为了支支持RTTTI和和 异常处理理,C+编译译器会插插入额外外的代码码。这样样就增加加了可执执行体的的大小,从从而使得得效率有有 所下降。当应用用纯C代代码的时时候,那那些额外外的代码码是不需需要的,所所以你可可以通过过关闭来来避免 它。 2 内内联汇编编 对时时间要求求苛刻的的部分可可以用
36、本本地汇编编来重写写。结果果可能是是速度上上的显著著提高。然而 ,这个方方法不能能想当然然的就去去实施,因因为它将将使得将将来的修修改非常常的困难难。维护护代码的的程 序员可能能对汇编编并不了了解。如如果想要要把软件件运行于于其他平平台也需需要重写写汇编代代码部分分。另 外,开发发和测试试汇编代代码是一一件辛苦苦的工作作,它将将花费更更长的时时间。 3 直直接和操操作系统统进行交交互 APPI函数数可以使使你直接接与操作作系统进进行交互互。有时时,直接接执行一一个系统统命令可可能会快快许 多。出于于这个目目的,你你可以使使用标准准函数ssysttem()。例例如,在在一个ddos/winndowws系统统下,你你 可以这样样显示当当前目录录下的文文件: #iinclludee ussingg naamesspacce sstd; innt mmainn() ssysttem(diir); /exxecuute thee ddir coommaand 注意:这这里是在在速度和和可移植植性以及及可扩展展性之间间做出的的折衷 希望二频频的编程程爱好者者跟帖,谈谈自自己的见见解。 tarssen转转帖自雁雁塔晨钟钟bbss