欢迎来到淘文阁 - 分享文档赚钱的网站! | 帮助中心 好文档才是您的得力助手!
淘文阁 - 分享文档赚钱的网站
全部分类
  • 研究报告>
  • 管理文献>
  • 标准材料>
  • 技术资料>
  • 教育专区>
  • 应用文书>
  • 生活休闲>
  • 考试试题>
  • pptx模板>
  • 工商注册>
  • 期刊短文>
  • 图片设计>
  • ImageVerifierCode 换一换

    C代码优化经验总结.docx

    • 资源ID:64533256       资源大小:26.35KB        全文页数:26页
    • 资源格式: DOCX        下载积分:15金币
    快捷下载 游客一键下载
    会员登录下载
    微信登录下载
    三方登录下载: 微信开放平台登录   QQ登录  
    二维码
    微信扫一扫登录
    下载资源需要15金币
    邮箱/手机:
    温馨提示:
    快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
    如填写123,账号就是123,密码也是123。
    支付方式: 支付宝    微信支付   
    验证码:   换一换

     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    C代码优化经验总结.docx

    C+代码优化经验总结优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法 的效率,况且我也没有这个能力。我只是想把一些能够简单的应用到你 的C+代码中的优化技术总结在这里,这样,当你遇到几种不一致的编 程策略的时候,就能够对每种策略的性能进行一个大概的估计。这也是 本文的目的之所在.目录:一.优化之前二.声明的放置三.内联函数.优化你的内存使用四 .速度优化.最后的求助一.优化之前在进行优化之前,我们首先应该做的是发现我们代码的瓶颈(bottleneck)在哪里。1 .我们先来看看内联函数给我们带来的好处:从一个用户的角度来看,内联函数看起来与普通函数一样,它能够有参数与返回值,也能够有自己的作用域, 然而它却不可能引入通常函数调用所带来的负担。另外,它能够比宏更安全更容易调试。当然有一点应该意识到,inline specifier仅仅是对编译器的 建议,编译器有权利忽略这个建议。那么编译器是如何决定函数内联与否呢?通常情况下 关键性因素包含函数体的大小,是否有局部对象被声明,函数的复杂性等等。2 .那么假如一个函数被声明为inline但是却没有被内联将会发生 什么呢?理论上,当编译器拒绝内联一个函数的时候,那个函数会像普通函数一样被对待,但是还会出现一些其他的问题。比如下面这段代码:/ Time, h#include<ctime>#include<iostream>using namespace std;class Time(public:inline void Show() for (int i = 0; i<10; i+) cout<<time(0)<<endl;);由于成员函数Time: Show()包含一个局部变量与一个for循环,因 此编译器通常拒绝inline,同时把它当作一个普通的成员函数。但是这个包含类声明的头文 件会被单独的#include进各个独立的编译单元中:/ fl. cpp1 i "11 T *1 ffmclude 1 ime. njvoid fl ()Time tl;tl. Show();)/ f2. cppttinclude "Time. hvoid f2 ()(Time t2;t2. Show();)结果编译器为这个程序生成了两个相同成员函数的拷贝:void fl ();void f2 ();int main ()fl();f2();return 0;)当程序被链接的时候,linker将会面对两个相同的Time: :Show。拷贝,因此函数重定义的连接错误发生。但是老一些的C+实现应付这种情况的办法是通过把一个un-inlined函数当作static来处理。因此每一份函数拷贝仅仅在自己的编译单元 中可见,这样链接错误就解决了,但是在程序中却会留下多份函数拷贝。在这种情况下,程序的性能不但没有提升,反而增加了编译与链接时间与最终可执行体的大小。但是幸运的是,新的C+标准中关于un-inlined函数的说法已 经改变。一个符合标准C+实现应该只生成一份函数拷贝。然而,要想所有的编译器都支持这 一点可能还需要很长时间。另外关于内联函数还有两个更令人头疼的问题。第一个问题是 该如何进行保护。一个函数开始的时候可能以内联的形式出现,但是随着系统的扩展,函 数体可能要求添加额外的功能,结果内联函数就变得不太可能,因此需要把inline specifier去除与把函数体放到一个单独的源文件中。另一个问题是当内联函数被应用在代码 库的时候产生。当内联函数改变的时候,用户务必重新编译他们的代码以反映这种改变。然而关于一个非内联函数,用户仅仅需要重新链接就能够了。这里想要说的是,内联函数并不是一个增强性能的灵丹妙药。只有当函数非常短小的时候它才能得到我们想要的效果,但是假如函数并不是很短而且在 很多地方都被调用的话,那么将会使得可执行体的体积增大。最令人烦恼的还是当编译器 拒绝内联的时候。在老的实现中,结果很不尽人意,尽管在新的实现中有很大的改善,但 是仍然还是不那么完善的。一些编译器能够足够的聪明来指出什么函数能够内联什么不能, 但是,大多数编译器就不那么聪明了,因此这就需要我们的经验来推断。假如内联函数 不能增强行能,就避免使用它!四.优化你的内存使用通常优化都有几个方面:更快的运行速度,有效的系统资源使用,更小的内存使用。通常情况下,代码优化都是试图在以上各个方面进行改善。重新放 置声明技术被证明是消除多余对象的建立与销毁,这样既减小了程序的大小又加快了运行 速度。然而其他的优化技术都是基于一个方面更快的速度或者者是更小的内存使用。有的时候,这些目标是互斥的,压缩了内存的使用往往却减慢了代码速度,快速的代码却又需 要更多的内存支持。下面总结两种在内存使用上的优化方法:1. Bit Fields在C/C+中都能够存取与访问数据的最小构成单元:bito由于 bit并不是C/C+基本的存取单元,因此这里是通过牺牲运行速度来减少内存与辅助存储器 的空间的使用。注意:一些硬件结构可能提供了特殊的处理器指令来存取bit,因此bitfields是否影响程序的速度取决于具体平台。在我们的现实生活中,一个数据的许多位都被浪费了,由于某些应用根本就不可能有那么大的数据范围。也许你会说,bit是如此之小,通过它就能减小 存储空间的使用吗?的确,在数据量很小的情况下不可能看出什么效果,但是在数据量惊人 的情况下,它所节约的空间还是能够让我们的眼睛为之一亮的。也许你又会说,现在内存与 硬盘越来越便宜,何苦要费半天劲,这省不了几个钱。但是还有另外一个原因一定会使你 信服,那就是数字信息传输。一个分布式数据库都会在不一致的地点有多份拷贝。那么数 百万的纪录传输就会显得十分昂贵。Ok,现在我们就来看看该如何做吧,首先看下面这段代 码:struct BillingReclong cust_id;long timestamp;enum CallType toll_free, local, regional, long_distance, international, cellular type;enum CallTariff off_peak, medium_rate, peak_time tariff;);上面这个结构体在32位的机器上将会占用16字节,你会发现其中 有许多位都被浪费了,尤其是那两个enum型,浪费更是严重,因此请看下面做出的改进:struct BillingRec(int custid: 24; / 23 bits + 1 sign bitint timestamp: 24;enum CallType/. . .);enum CallTariff/.然而当你做这件情况的时候切忌从一个debug-version进行推断,由于 debug-version 中包含了许多额外的代码。一个debug-version可执行体要比release-version 大出 40%。那些额外的代码都是用来支持调试的,比如说符号的查找。大多数实现都为 debug-version 与 release-version提供了不一致的operator new与库函数。而且,一个release-version 的执彳亍体可能已经通过多种途径进行了优化,包含不必要的临时对象的消 除,循环展开,把对象移入寄存器,内联等等。另外,我们要把调试与优化区分开来,它们是在完成不一致的 任务。 debug-version 是用来追捕bugs与检查程序是否有逻辑上的问题。release-version 则是用来做一些性能上的调整与进行优化。下面就让我们来看看有什么代码优化技术吧!unsigned call: 3;unsigned tariff: 2;);现在一个数据从16字节缩减到了 8字节,减少了一半,怎么样,效 果还是显著的吧:)2. UnionsUnions通过把两个或者更多的数据成员放置在相同地址的内存中来减少内存浪费,这就要求在任何时间只能有一个数据成员有效。Union能够有成员函数, 包含构造函数与析构函数,但是它不能有虚函数。C+支持anonymous unionso anonymous union是一个未命名类型的未命名对象。比如:union long n; void * p; / anonymousn 二 lOOOL; / members are directly accessed p = 0; / n is now also 0不像命名的union,它不能有成员函数与非public的数据成员。那么unions什么时候是有用的呢?下面这个类从数据库中获取一 个人的信息。关键字既可以是一个特有的ID或者者人名,但是二者却不能同时有效:class PersonalDetailsprivate:char * name;long ID;/.public:PersonalDetails (const char *nm); /key is of type char * usedPersonalDetails(long id) : ID(id) /numeric key used上面这段代码中就会造成内存的浪费,由于在一个时间只能有一个 关键字有效。anonymousunion能够在这里使用来减少内存的使用,比如:class PersonalDetailsprivate:union /anonymouschar * name;long ID;public:PersonalDetails (const char *nm);PersonalDetails(long id) : ID (id) /*/ / directaccess to a member;通过使用union, PersonalDetails类的大小被减半。但是这里要说 明的是,节约4个字节内存并不值得引入union所带来的烦恼,除非这个类作为数百万数 据库记录的类型或者者纪录在一条很慢的通信线路传输。值得注意的是unions并不引入任何运 行期负担,因此这里不会有什么速度上的缺失。anonymous union的优点就是它的成员能 够被直接访问。五.速度优化在一些对速度要求非常苛刻的应用系统中,每一个CPU周期都 是要争取的。这个部分展现了 一些简单方法来进行速度优化。1 .使用类来包裹长的参数列表一个函数调用的负担将会随着参数列表的增长而增加。运行时 系统不得不建立堆栈来存储参数值;通常,当参数很多的时候,这样一个操作就会花费很 长的时间。把参数列表包裹进一个单独的类中同时通过引用进行传递,这 样将会节约很多的时间O当然,假如函数本身就很长,那么建立堆栈的时间就能够忽略了, 因此也就没有必要这样做。然而,关于那些执行时间很短而且经常被调用的函数来说, 包裹一个长的参数列表在对象中同时通过引用传递将会提高性能。2 .寄存器变量register specifier被用来告诉编译器一个对象将被会非常多 的使用,能够把它放入寄存器中。比如:void f () int *p = new int3000000;register int *p2 = p; /store the address in a register for (register int j = 0; j<3000000; j+)(*p2+ = 0;)/. . . use pdelete p;)循环计数是应用寄存器变量的最好的候选者。当它们没有被存入一个寄存器中,大部分的循环时间都被用在了从内存中取出变量与给变量赋新值上。假 如把它存入一个寄存器中的话,将会大大减少这种负担。需要注意的是,register specifier仅仅是对编译器的一个建议。就好比内联函数一样,编译器能够拒绝把一个对象存储 到寄存器中。另外,现代的编译器都会通过把变量放入寄存器中来优化循环计数。Register storage specifier并不仅仅局限在基本类型上,它能够被应用于任何类型的对象。假 如对象太大而不能装进寄存器的话,编译器仍然能够把它放入一个高速存储器中,比如 cacheo用register storage specifier声明函数型参将会是建议编译 器把实参存入寄存器中而不是堆栈中。比如:void f (register int j, register Date d);3 .把那些保持不变的对象声明为const通过把对象声明为const,编译器就能够利用这个声明把这样 一个对象放入寄存器中。4 . Virtual function的运行期负担当调用一个virtual function,假如编译器能够解决调用的静 态化,将不可能引入额外的负担。另外,一个非常短的虚函数能够被内联处理。在下面这个 例子中,一个聪明的编译器能够做到静态调用虚函数:ttinclude <iostream>using namespace std;class V(public:virtual void show() const coutI'ni V<endl; ;class W : public V public:void show() const coutVTm Wz,<<endl;);void f (V & v, V *pV)(v. show();pV->show();)void g()(V v;f(V, &v);)int main ()g();return 0;假如整个程序出现在一个单独的编译单元中,编译器能够对main ()中的g ()进行内联替换。同时在g()中f()的调用也能够被内联处理。由于传给f()的参 数的动态类型能够在编译期被知晓,因此编译器能够把对虚函数的调用静态化。但是不能保 证每个编译器都这样做O然而,一些编译器确实能够利用在编译期获得参数的动态类型从 而使得函数的调用在编译期间就确定了下来,避免了动态绑定的负担。5. Function objects VS function pointers用function objects取代function pointers的好处不仅仅局限在 能够泛化与简单的保护性上。而且编译器能够对function object的函数调用进行内联处理, 从而进一步的增强了性二.声明的放置程序中变量与对象的声明放在什么位置将会对性能产生显著影 响。同样,对postfix与prefix运算符的选择也会影响性能。这一部分我们集中讨论四个问 题:初始化v. s赋值,在程序确实要使用的地方放置声明,构造函数的初始化列表,pref ix v. s postfix 运算符(1)请使用初始化而不是赋值在C语言中只同意在一个函数体的开头进行变量的声明,然而 在C+中声明能够出现在程序的任何位置。这样做的目的是希望把对象的声明拖延到确实要 使用它的时候再进行。这样做能够有两个好处:1.确保了对象在它被使用前不可能被程序 的其他部分恶意修改。如果对象在开头就被声明然而却在20行以后才被使用的话,就不能做 这样的保证。2.使我们能六.最后的求助迄今为止为大家展示的优化技术并没有在设计与代码的可读性 上做出妥协。事实上,它们中的一些还提高了软件的稳固性与可保护性。但是在一些对 时间与内存有严格限制的软件开发中,上面的技术可能还不够;有可能还需要一些会影响 软件的可移植性与扩展性的技术。但是这些技术只能在所有其他的优化技术都被应用但是 还不符合要求的情况下使用。1 .关闭RTTI与特殊处理支持当你导入纯C代码给C+编译器的时候,你可能会发现有一些 性能上的缺失。这并不是语言或者者编译器的错误,而是编译器作出的一些调整。假如你想 获得与C编译器同样的性能,那么请关闭编译器对RTTI与特殊处理的支持。为什么会这样呢?由于为了支持RTTI与特殊处理,C+编译器会插入额外的代码。这样就增加了可执行体的 大小,从而使得效率有所下降。当应用纯C代码的时候,那些额外的代码是不需要的,因 此你能够通过关闭来避免它。2 .内联汇编对时间要求苛刻的部分能够用本地汇编来重写。结果可能是速 度上的显著提高。然而,这个方法不能想当然的就去实施,由于它将使得将来的修改非常 的困难。保护代码的程序员可能对汇编并不熟悉。假如想要把软件运行于其他平台也需要 重写汇编代码部分。另外,开发与测试汇编代码是一件辛苦的工作,它将花费更长的时间。3 .直接与操作系统进行交互API函数能够使你直接与操作系统进行交互。有的时候,直接执行一个系统命令可能会快许多。出于这个目的,你能够使用标准函数system。比如,在一个 dos/windows系统下,你能够这样显示当前目录下的文件:ttinclude <cstdlib>using namespace std;int main ()(system(dir); /execute the dir command)注意:这里是在速度与可移植性与可扩展性之间做出的折衷希望二频的编程爱好者跟帖,谈谈自己的见解。tarsen转帖自雁塔晨钟bbs有机会通过用初始化取代赋值来达到性能的提升,从前声明只能放在开头,然而往往开始的时候我们还没有获得我们想要的值,因此初始化所带来的好处就 无法被应用。但是现在我们能够在我们获得了想要的值的时候直接进行初始化,从而省去 了一步。注意,或者许对于基本类型来说,初始化与赋值之间可能不可能有什么差异,但是 关于用户定义的类型来说,二者就会带来显著的不一致,由于赋值会多进行一次函数调用operator二。 因止匕当我们在赋值与初始化之间进行选择的话,初始化应该是我们的首选。(2)把声明放在合适的位置上在一些场合,通过移动声明到合适的位置所带来的性能提升应 该引起我们足够的重视o比如:bool is_C_Needed ();void use ()C cl;if (is_C_Needed() = false)(return; /cl was not needed/use cl herereturn;上面这段代码中对象Cl即使在有可能不使用它的情况下也会被创 建,这样我们就会为它付出不必要的花费,有可能你会说一个对象C1能浪费多少时间,但是 假如是这种情况呢:Ccl 1000;我想就不是说浪费就浪费了。但是我们能够通过移动声明 cl的位置来改变这种情况:void use ()(if (is_C_Needed()二二 false)(return; /cl was not neededC cl; /moved from the block's beginning/use cl herereturn;)怎么样,程序的性能是不是已经得到很大的改善了呢?因此请认真分析你的代码,把声明放在合适的位置上,它所带来的好处是你难以想象的。(3)初始化列表我们都明白,初始化列表通常是用来初始化const或者者reference数据成员。但是由于他自身的性质,我们能够通过使用初始化列表来实现性能的提升。我们先来看一段程序:class Personprivate:C c_l;C c_2;public:Person (const C& cl, const C& c2 ) : c_l (cl), c_2 (c2) ;当然构造函数我们也能够这样写:Person:Person(const C& cl, const C& c2) c_2 = c2;)那么毕竟二者会带来什么样的性能差异呢,要想搞清晰这个问题,我们首先要搞清晰二者是如何执行的,先来看初始化列表:数据成员的声明操作都是在构 造函数执行之前就完成了,在构造函数中往往完成的只是赋值操作,然而初始化列表直接 是在数据成员声明的时候就进行了初始化,因此它只执行了一次copy constructor。再来 看在构造函数中赋值的情况:首先,在构造函数执行前会通过default constructor创建 数据成员,然后在构造函数中通过operator二进行赋值。因此它就比初始化列表多进行了一 次函数调用。性能差异就出来了。但是请注意,假如你的数据成员都是基本类型的话,那 么为了程序的可读性就不要使用初始化列表了,由于编译器对两者产生的汇编代码是相同 的。4 4) postfix VS prefix 运算符prefix运算符+与一比它的postfix版本效率更高,由于当postfix运算符被使用的时候,会需要一个临时对象来储存改变往常的值。关于基本类型,编 译器会消除这一份额外的拷贝,但是关于用户定义类型,这大概是不可能的。因此请你尽 可能使用prefix运算符O三.内联函数内联函数既能够去除函数调用所带来的效率负担又能够保留通 常函数的优点。然而,内联函数并不是万能药,在一些情况下,它甚至能够降低程序的性 能。因此在使用的时候应该慎重。

    注意事项

    本文(C代码优化经验总结.docx)为本站会员(太**)主动上传,淘文阁 - 分享文档赚钱的网站仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知淘文阁 - 分享文档赚钱的网站(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    关于淘文阁 - 版权申诉 - 用户使用规则 - 积分规则 - 联系我们

    本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

    工信部备案号:黑ICP备15003705号 © 2020-2023 www.taowenge.com 淘文阁 

    收起
    展开