《2022年C++代码优化经验总结.docx》由会员分享,可在线阅读,更多相关《2022年C++代码优化经验总结.docx(32页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、精品学习资源C+ 代码优化体会总结优化是一个特殊大的主题,本文并不是去深化探讨性能分析理论,算法的效率, 况且我也没有这个才能; 我只是想把一些可以简洁的应用到你的 C+代码中的优化技术总结在这里,这样,当你遇到几种不同的编程策略的时候, 就可以对每种策略的性能进行一个大致的估量;这也是本文的目的之所在 .目录:一.优化之前二.声明的放置三.内联函数四.优化你的内存使用五.速度优化六.最终的求助一.优化之前在进行优化之前,我们第一应当做的是发觉我们代码的瓶颈( bottleneck)在哪里;欢迎下载精品学习资源然而当你做这件事情的时候切忌从一个debug-version进行推断, 由于 deb
2、ug-version中包含了许多额外的代码;一个debug-version可执行体要比release-version大出 40%;那些额外的代码都是用来支持调试的,比如说符号的查找;大多数实现都为 debug-version和 release-version供应了不同的 operatornew以及库函数;而且,一个release-version的执行体可能已经通过多种途径进行了优化,包括不必要的临时对象的排除,循环开放,把对象移入寄存器,内联等等;另外,我们要把调试和优化区分开来,它们是在完成不同的任务; debug-version是用来追捕 bugs 以及检查程序是否有规律上的问题;rele
3、ase-version就是用来做一些性能上的调整以及进行优化;下面就让我们来看看有哪些代码优化技术吧!欢迎下载精品学习资源二.声明的放置程序中变量和对象的声明放在什么位置将会对性能产生显著影响;同样,对 postfix和prefix运算符的选择也会影响性能;这一部分我们集中争辩四个问题:初始化 v.s赋值,在程序的确要使用的地方放置声明,构造函数的初始化列表, prefixv.s postfix运算符;( 1) 请使用初始化而不是赋值在 C语言中只答应在一个函数体的开头进行变量的声明,然而在 C+中声明可以显现在程序的任何位置;这样做的目的是期望把对象的声明拖延到的确要使用它的时候再进行;这样
4、做可以有两个好处: 1.确保了对象在它被使用前不会被程序的其他部分恶意修改;如果对象在开头就被声明然而却在20 行以后才被使用的话, 就不能做这样的保证; 2.使我们欢迎下载精品学习资源有机会通过用初始化取代赋值来达到性能的提升,从前声明只能放在开头,然而往往开头的时候我们仍没有获得我们想要的值,因此初始化所带来的好处就无法被应用;但是现在我们可以在我们获得了想要的值的时候直接进行初始化,从而省去了一步;留意,或许对于基本类型来说,初始化和赋值之间可能不会有什么差异,但是对于用户定义的类型来说,二者就会带来显著的不同,由于赋值会多进行一次函数调用-operator =;因此当我们在赋值和初始化
5、之间进行选择的话,初始化应当是我们的首选;( 2) 把声明放在合适的位置上在一些场合,通过移动声明到合适的位置所带来的性能提升应当引起我们足够的重视;例如:bool is_C_Needed;欢迎下载精品学习资源void useC c1;if is_C_Needed = falsereturn; /c1 was not needed/use c1 herereturn;上面这段代码中对象 c1 即使在有可能不使用它的情形下也会被创建,这样我们就会为它付出不必要的花费, 有可能你会说一个对象 c1 能铺张多少时间, 但是假如是这种情形呢: Cc11000;我想就不是说铺张就铺张了; 但是我们可以通
6、过移动声明c1 的位置来转变这种情欢迎下载精品学习资源况:void useif is_C_Needed = falsereturn; /c1 was not neededC c1; /moved from the blocks beginning/use c1 herereturn;怎么样,程序的性能是不是已经得到很大的改善了呢?因此请仔细分析你的代码,把声明放在合适的位置上,它所带来的好处是你难以想象的;( 3) 初始化列表欢迎下载精品学习资源我们都知道,初始化列表一般是用来初始化const 或者reference数据成员;但是由于他自身的性质,我们可以通过使用初始化列表来实现性能的提升;我
7、们先来看一段程序:class Personprivate:C c_1;C c_2;public:Personconst C& c1, const C& c2 : c_1c1, c_2c2 ;当然构造函数我们也可以这样写:Person:Personconst C& c1, const C& c2欢迎下载精品学习资源c_1 = c1;c_2 = c2;那么究竟二者会带来什么样的性能差异呢,要想搞清楚这个问题, 我们第一要搞清楚二者是如何执行的,先来看初始化列表:数据成员的声明操作都是在构造函数执行之前就完成了,在构造函数中往往完成的只是赋值操作,然而初始化列表直接是在数据成员声明的时候就进行了初始
8、化,因此它只执行了一次copy constructor;再来看在构造函数中赋值的情形:第一,在构造函数执行前会通过default constructor创建数据成员,然后在构造函数中通过 operator=进行赋值;因此它就比初始化列表多进行了一次函数调用;性能差异就出来了;但是请留意,假如你的数据成员都是基本类型的话,那么为了程序的可读性就欢迎下载精品学习资源不要使用初始化列表了,由于编译器对两者产生的汇编代码是相同的;( 4) postfix VS prefix运算符prefix运算符+和比它的 postfix版本效率更高,由于当postfix运算符被使用的时候,会需要一个临时对象来储存转
9、变以前的值;对于基本类型,编译器会排除这一份额外的拷贝,但是对于用户定义类型,这似乎是不行能的;因此请你尽可能使用 prefix运算符;三.内联函数内联函数既能够去除函数调用所带来的效率负担又能够保留一般函数的优点;然而,内联函数并不是万能药,在一些情形下,它甚至能够降低程序的性能;因此在使用的时候应当谨慎;欢迎下载精品学习资源1. 我们先来看看内联函数给我们带来的好处:从一个用户的角度来看,内联函数看起来和一般函数一样,它可以有参数和返回值,也可以有自己的作用域, 然而它却不会引入一般函数调用所带来的负担;另外,它可以比宏更安全更简洁调试;当然有一点应当意识到, inlinespecifie
10、r仅仅是对编译器的建议,编译器有权益忽略这个建议;那么编译器是如何准备函数内联与否呢?一般情形下关键性因素包括函数体的大小,是否有局部对象被声明,函数的复杂性等等;2. 那么假如一个函数被声明为inline但是却没有被内联将会发生什么呢?理论上,当编译器拒绝内联一个函数的时候,那个函数会像一般函数一样被对待, 但是仍会显现一些其他的问题;例如下面这段代码:/ filename Time.h欢迎下载精品学习资源#include#includeusing namespace std;class Timepublic:inline void Show for int i = 0; i10; i+ c
11、outtime0endl;由于成员函数 Time:Show 包括一个局部变量和一个for循环,所以编译器一般拒绝 inline,并且把它当作一个一般的成员函数;但是这个包含类声明的头文件会被单独的 #include进各个独立的编译单元中:/ filename f1.cpp#include Time.hj欢迎下载精品学习资源void f1Time t1;t1.Show;/ filename f2.cpp#include Time.hvoid f2Time t2;t2.Show;结果编译器为这个程序生成了两个相同成员函数的拷贝:void f1;void f2;欢迎下载精品学习资源int mainf
12、1;f2;return 0;当程序被链接的时候,linker将会面对两个相同的Time:Show 拷贝,于是函数重定义的连接错误发生;但是老一些的C+实现应对这种情形的方法是通过把一个 un-inlined函数当作 static来处理;因此每一份函数拷贝仅仅在自己的编译单元中可见,这样链接错误就解决了,但是在程序中却会留下多份函数拷贝;在这种情形下, 程序的性能不但没有提升,反而增加了编译和链接时间以及最终可执行体的大小;欢迎下载精品学习资源但是幸运的是, 新的 C+标准中关于 un-inlined函数的说法已经转变;一个符合标准C+实现应当只生成一份函数拷贝; 然而,要想全部的编译器都支持这
13、一点可能仍需要很长时间;另外关于内联函数仍有两个更令人头疼的问题;第一个问题是该如何进行爱护;一个函数开头的时候可能以内联的形式显现,但是随着系统的扩展,函数体可能要求添加额外的功能,结果内联函数就变得不太可能,因此需要把inline specifier去除以及把函数体放到一个单独的源文件中;另一个问题是当内联函数被应用在代码库的时候产生;当内联函数转变的时候,用户必需重新编译他们的代码以反映这种转变;然而对于一个非内联函数,用户仅仅需要重新链接就可以了;欢迎下载精品学习资源这里想要说的是,内联函数并不是一个增强性能的灵丹妙药;只有当函数特殊短小的时候它才能得到我们想要的成效,但是假如函数并不
14、是很短而且在许多地方都被调用的话,那么将会使得可执行体的体积增大;最令人苦恼的仍是当编译器拒绝内联的时候;在老的实现中,结果很不尽人意,虽然在新的实现中有很大的改善,但是仍然仍是不那么完善的;一些编译器能够足够的聪慧来指出哪些函数可以内联哪些不能, 但是,大多数编译器就不那么聪慧了,因此这就需要我们的体会来判定;假如内联函数不能增强行能,就防止使用它!四.优化你的内存使用通常优化都有几个方面:更快的运行速度,有效的系统资源使用,更小的内存使用;欢迎下载精品学习资源一般情形下,代码优化都是试图在以上各个方面进行改善;重新放置声明技术被证明是消除余外对象的建立和销毁,这样既减小了程序的大小又加快了
15、运行速度;然而其他的优化技术都是基于一个方面 -更快的速度或者是更小的内存使用;有时,这些目标是互斥的,压缩了内存的使用往往却减慢了代码速度,快速的代码却又需要更多的内存支持;下面总结两种在内存使用上的优化方法:1. Bit Fields在 C/C+中都可以存取和拜望数据的最小组成单元:bit ;由于bit并不是 C/C+基本的存取单元,所以这里是通过牺牲运行速度来削减内存和帮忙储备器的空间的使用;留意:一些硬件结构可能供应了特殊的处理器指令来存取bit ,因此 bit fields是否影响程序的速度取决于具体平台;欢迎下载精品学习资源在我们的现实生活中,一个数据的许多位都被铺张了,由于某些应
16、用根本就不会有那么大的数据范畴;或许你会说,bit是如此之小,通过它就能减小储备空间的使用吗?的确,在数据量很小的情形下不会看出什么成效,但是在数据量惊人的情形下,它所节省的空间仍是能够让我们的眼睛为之一亮的;或许你又会说,现在内存和硬盘越来越廉价,何苦要费半天劲,这省不了几个钱;但是仍有另外一个缘由确定会使你信服,那就是数字信息传输;一个分布式数据库都会在不同的地点有多份拷贝;那么数百万的纪录传输就会显得特殊昂贵; Ok,现在我们就来看看该如何做吧,第一看下面这段代码:struct BillingReclong cust_id;欢迎下载精品学习资源long timestamp;enum Ca
17、llTypetoll_free,local,regional,long_distance,international,cellular type;enum CallTariffoff_peak,medium_rate,peak_time欢迎下载精品学习资源 tariff;上面这个结构体在 32 位的机器上将会占用 16 字节,你会发觉其中有许多位都被铺张了,尤其是那两个 enum型,铺张更是严肃,所以请看下面做出的改进: struct BillingRecint cust_id: 24; / 23 bits + 1 sign bitint timestamp: 24;enum CallType
18、/.;enum CallTariff/.;欢迎下载精品学习资源unsigned call: 3;unsigned tariff: 2;现在一个数据从 16 字节缩减到了 8 字节, 削减了一半, 怎么样, 成效仍是显著的吧:)2. UnionsUnions 通过把两个或更多的数据成员放置在相同地址的内存中来削减内存铺张,这就要求在任何时间只能有一个数据成员有效;Union可以有成员函数, 包括构造函数和析构函数,但是它不能有虚函数; C+支持 anonymousunions ;anonymous union 是一个未命名类型的未命名对象;例如:union long n; void * p; /
19、 anonymousn = 1000L; / members are directly accessedp = 0; / n is now also 0欢迎下载精品学习资源不像命名的 union ,它不能有成员函数以及非public的数据成员;那么 unions 什么时候是有用的呢?下面这个类从数据库中猎取一个人的信息;关键字既可以是一个特有的 ID 或者人名,但是二者却不能同时有效:class PersonalDetailsprivate:char * name;long ID;/.public:PersonalDetailsconst char *nm; /key is of type c
20、har* usedPersonalDetailslongid: IDid/numerickey used;欢迎下载精品学习资源上面这段代码中就会造成内存的铺张,由于在一个时间只能有一个关键字有效; anonymousunion 可以在这里使用来削减内存的使用,例如:class PersonalDetailsprivate:union /anonymouschar * name;long ID;public:PersonalDetailsconst char *nm;PersonalDetailslong id : IDid /*/ / direct access to a member欢迎下载
21、精品学习资源/.;通过使用 union ,PersonalDetails类的大小被减半; 但是这里要说明的是,节省 4 个字节内存并不值得引入 union 所带来的麻烦,除非这个类作为数百万数据库记录的类型或者纪录在一条很慢的通信线路传输; 值得留意的是 unions 并不引入任何运行期负担,所以这里不会有什么速度上的缺失; anonymous union 的优点就是它的成员可以被直接拜望;五.速度优化在一些对速度要求特殊苛刻的应用系统中,每一个CPU周期都是要争取的;这个部分展现了一些简洁方法来进行速度优化;1. 使用类来包裹长的参数列表欢迎下载精品学习资源一个函数调用的负担将会随着参数列表
22、的增长而增加;运行时系统不得不建立堆栈来储备参数值;通常,当参数许多的时候,这样一个操作就会花费很长的时间;把参数列表包裹进一个单独的类中并且通过引用进行传递,这样将会节省许多的时间;当然, 假如函数本身就很长, 那么建立堆栈的时间就可以忽视了, 因此也就没有必要这样做;然而,对于那些执行时间很短而且经常被调用的函数来说, 包裹一个长的参数列表在对象中并且通过引用传递将会提高性能;2. 寄存器变量registerspecifier被用来告知编译器一个对象将被会特殊多的使用,可以把它放入寄存器中;例如:void f欢迎下载精品学习资源int *p = new int3000000;registe
23、r int *p2 = p; /store the address in a registerfor register int j = 0; j3000000; j+*p2+ = 0;/.use pdelete p;循环计数是应用寄存器变量的最好的候选者;当它们没有被存入一个寄存器中,大部分的循环时间都被用在了从内存中取出变量和给变量赋新值上;假如把它存入一个寄存器中的话,将会大大削减这种负担;需要留意的是,register specifier仅仅是对编译器的欢迎下载精品学习资源一个建议;就好比内联函数一样,编译器可以拒绝把一个对象储备到寄存器中;另外,现代的编译器都会通过把变量放入寄存器中来
24、优化循环计数;Register storage specifier并不仅仅局限在基本类型上,它能够被应用于任何类型的对象;假如对象太大而不能装进寄存器的话,编译器仍然能够把它放入一个高速储备器中,例如cache;用 registerstoragespecifier声明函数型参将会是建议编译器把实参存入寄存器中而不是堆栈中;例如:void fregister int j, register Date d;3. 把那些保持不变的对象声明为const通过把对象声明为 const ,编译器就可以利用这个声明把这样一个对象放入寄存器中;4. Virtual function的运行期负担欢迎下载精品学习资
25、源当调用一个 virtualfunction,假如编译器能够解决调用的静态化,将不会引入额外的负担;另外,一个特殊短的虚函数可以被内联处理;在下面这个例子中,一个聪慧的编译器能够做到静态调用虚函数:#include using namespace std;class Vpublic:virtual void show const coutIm Vendl; ;class W : public Vpublic:欢迎下载精品学习资源void show const coutIm Wshow;void gV v;fv, &v;int maing;欢迎下载精品学习资源return 0;假如整个程序显现在
26、一个单独的编译单元中,编译器能够对main 中的 g 进行内联替换;并且在 g 中 f 的调用也能够被内联处理;由于传给f的参数的动态类型能够在编译期被知晓,因此编译器能够把对虚函数的调用静态化;但是不能保证每个编译器都这样做;然而,一些编译器的确能够利用在编译期获得参数的动态类型从而使得函数的调用在编译期间就确定了下来,防止了动态绑定的负担;5. Function objects VS function pointers用 functionobjects取代 functionpointers的好处不仅仅局限在能够泛化和简洁的爱护性上;而且编译器能够对 functionobject的函数调用进
27、行内联处理, 从而进一步的增强了性欢迎下载精品学习资源能六.最终的求助迄今为止为大家呈现的优化技术并没有在设计以及代码的可读性上做出妥协;事实上,它们中的一些仍提高了软件的稳固性和可爱护性;但是在一些对时间和内存有严格限制的软件开发中,上面的技术可能仍不够;有可能仍需要一些会影响软件的可移植性和扩展性的技术;但是这些技术只能在全部其他的优化技术都被应用但是仍不符合要求的情形下使用;1. 关闭 RTTI 和反常处理支持当你导入纯 C代码给 C+编译器的时候,你可能会发觉有一些性能上的缺失;这并不是语言或者编译器的错误,而是编译器作出的一些调整;假如你想获得和 C编译器同样的性能欢迎下载精品学习资
28、源,那么请关闭编译器对RTTI 以及反常处理的支持;为什么会这样呢?由于为了支持 RTTI 和反常处理, C+编译器会插入额外的代码; 这样就增加了可执行体的大小,从而使得效率有所下降;当应用纯 C 代码的时候,那些额外的代码是不需要的,所以你可以通过关闭来防止它;2. 内联汇编对时间要求苛刻的部分可以用本地汇编来重写;结果可能是速度上的显著提高;然而,这个方法不能想当然的就去实施,由于它将使得将来的修改特殊的困难;爱护代码的程序员可能对汇编并不明白;假如想要把软件运行于其他平台也需要重写汇编代码部分;另外,开发和测试汇编代码是一件辛苦的工作,它将花费更长的时间;3. 直接和操作系统进行交互欢迎下载精品学习资源API 函数可以使你直接与操作系统进行交互;有时,直接执行一个系统命令可能会快许多;出于这个目的,你可以使用标准函数system ;例如,在一个dos/windows 系统下,你可以这样显示当前目录下的文件:#include using namespace std;int mainsystemdir; /execute the dir command留意:这里是在速度和可移植性以及可扩展性之间做出的折衷期望二频的编程爱好者跟帖 , 谈谈自己的见解;tarsen转帖自雁塔晨钟 bbs欢迎下载