欢迎来到精优文库网!

C,,代码优化经验总结

文章来源:网友投稿 时间:2022-11-07 13:40:06

下面是小编为大家整理的C,,代码优化经验总结,供大家参考。

C,,代码优化经验总结

C++ 代码优化经验总结 优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法的效率,况且我也没有这个能力。我只是想把一些能够简单的应用到你的 C++代码中的优化技术总结在这里,这样,当你遇到几种不一致的编程策略的时候,就能够对每种策略的性能进行一个大概的估计。这也是本文的目的之所在. 目录:

   一. 优化之前 二. 声明的放置 三. 内联函数 四. 优化你的内存使用 五. 速度优化 六. 最后的求助 一. 优化之前 在进行优化之前,我们首先应该做的是发现我们代码的瓶颈(bottleneck)在哪里。

   然而当你做这件情况的时候切忌从一个 debug-version 进行推断,由于 debug-version 中包 含了许多额外的代码。一个 debug-version 可执行体要比release-version 大出 40%。那些额 外的代码都是用来支持调试的,比如说符号的查找。大多数实现都为 debug-version 与 rele ase-version 提供了不一致的 operator new 与库函数。而且,一个release-version 的执行 体可能已经通过多种途径进行了优化,包含不必要的临时对象的消除,循环展开,把对象 移入寄存器,内联等等。

   另外,我们要把调试与优化区分开来,它们是在完成不一致的任务。

  debug-version 是 用来追捕 bugs 与检查程序是否有逻辑上的问题。release-version则是用来做一些性能上 的调整与进行优化。

   下面就让我们来看看有什么代码优化技术吧! 二. 声明的放置 程序中变量与对象的声明放在什么位置将会对性能产生显著影响。同样,对 postfix 与 prefix 运算符的选择也会影响性能。这一部分我们集中讨论四个问题:初始化 v.s 赋值, 在程序确实要使用的地方放置声明,构造函数的初始化列表,prefix v.s postfix 运算符 。

   (1) 请使用初始化而不是赋值 在 C 语言中只同意在一个函数体的开头进行变量的声明,然而在 C++中声明能够出现在 程序的任何位置。这样做的目的是希望把对象的声明拖延到确实要使用它的时候再进行。

   这样做能够有两个好处:1. 确保了对象在它被使用前不可能被程序的其他部分恶意修改。如 果对象在开头就被声明然而却在 20 行以后才被使用的话,就不能做这样的保证。2. 使我们 有机会通过用初始化取代赋值来达到性能的提升,从前声明只能放在开头,然而往往开始 的时候我们还没有获得我们想要的值,因此初始化所带来的好处就无法被应用。但是现在 我们能够在我们获得了想要的值的时候直接进行初始化,从而省去了一步。注意,或者许对 于基本类型来说,初始化与赋值之间可能不可能有什么差异,但是关于用户定义的类型来说 ,二者就会带来显著的不一致,由于赋值会多进行一次函数调用----operator =。因此当我 们在赋值与初始化之间进行选择的话,初始化应该是我们的首选。

   (2) 把声明放在合适的位置上 在一些场合,通过移动声明到合适的位置所带来的性能提升应该引起我们足够的重视 。比如:

   bool is_C_Needed(); void use() { C c1; if (is_C_Needed() == false) { return; //c1 was not needed } //use c1 here return; } 上面这段代码中对象 c1 即使在有可能不使用它的情况下也会被创建,这样我们就会为它付 出不必要的花费,有可能你会说一个对象 c1 能浪费多少时间,但是假如是这种情况呢:C c1[1000];我想就不是说浪费就浪费了。但是我们能够通过移动声明c1 的位置来改变这种情 况:

   void use() { if (is_C_Needed() == false) { return; //c1 was not needed } C c1; //moved from the block"s beginning //use c1 here return; } 怎么样,程序的性能是不是已经得到很大的改善了呢?因此请认真分析你的代码,把声明 放在合适的位置上,它所带来的好处是你难以想象的。

   (3) 初始化列表 我们都明白,初始化列表通常是用来初始化 const 或者者reference 数据成员。但是由于 他自身的性质,我们能够通过使用初始化列表来实现性能的提升。我们先来看一段程序:

   class Person { private: C c_1; C c_2; public: Person(const C& c1, const C& c2 ): c_1(c1), c_2(c2) {} }; 当然构造函数我们也能够这样写:

   Person::Person(const C& c1, const C& c2) { c_1 = c1; c_2 = c2; } 那么毕竟二者会带来什么样的性能差异呢,要想搞清晰这个问题,我们首先要搞清晰二者 是如何执行的,先来看初始化列表:数据成员的声明操作都是在构造函数执行之前就完成 了,在构造函数中往往完成的只是赋值操作,然而初始化列表直接是在数据成员声明的时 候就进行了初始化,因此它只执行了一次 copy constructor。再来看在构造函数中赋值的 情况:首先,在构造函数执行前会通过 default constructor 创建数据成员,然后在构造函 数中通过 operator =进行赋值。因此它就比初始化列表多进行了一次函数调用。性能差异 就出来了。但是请注意,假如你的数据成员都是基本类型的话,那么为了程序的可读性就 不要使用初始化列表了,由于编译器对两者产生的汇编代码是相同的。

   (4) postfix VS prefix 运算符 prefix 运算符++与—比它的 postfix 版本效率更高,由于当postfix 运算符被使用的时 候,会需要一个临时对象来储存改变往常的值。关于基本类型,编译器会消除这一份额外 的拷贝,但是关于用户定义类型,这大概是不可能的。因此请你尽可能使用 prefix 运算符 。

   三. 内联函数 内联函数既能够去除函数调用所带来的效率负担又能够保留通常函数的优点。然而, 内联函数并不是万能药,在一些情况下,它甚至能够降低程序的性能。因此在使用的时候 应该慎重。

   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 进各个独立的编译单元中:

   // f1.cpp #include "Time.hj" void f1() { Time t1; t1.Show(); } // f2.cpp #include "Time.h" void f2() { Time t2; t2.Show(); } 结果编译器为这个程序生成了两个相同成员函数的拷贝:

   void f1(); void f2(); int main() { f1(); f2(); return 0; } 当程序被链接的时候,linker 将会面对两个相同的Time::Show()拷贝,因此函数重定 义的连接错误发生。但是老一些的 C++实现应付这种情况的办法是通过把一个 un-inlined 函 数当作 static 来处理。因此每一份函数拷贝仅仅在自己的编译单元中可见,这样链接错误 就解决了,但是在程序中却会留下多份函数拷贝。在这种情况下,程序的性能不但没有提 升,反而增加了编译与链接时间与最终可执行体的大小。

   但是幸运的是,新的 C++标准中关于 un-inlined 函数的说法已经改变。一个符合标准 C+ +实现应该只生成一份函数拷贝。然而,要想所有的编译器都支持这一点可能还需要很长时 间。

   另外关于内联函数还有两个更令人头疼的问题。第一个问题是该如何进行保护。一个 函数开始的时候可能以内联的形式出现,但是随着系统的扩展,函数体可能要求添加额外 的功能,结果内联函数就变得不太可能,因此需要把 inline specifier 去除与把函数体 放到一个单独的源文件中。另一个问题是当内联函数被应用在代码库的时候产生。当内联 函数改变的时候,用户务必重新编译他们的代码以反映这种改变。然而关于一个非内联函 数,用户仅仅需要重新链接就能够了。

   这里想要说的是,内联函数并不是一个增强性能的灵丹妙药。只有当函数非常短小的 时候它才能得到我们想要的效果,但是假如函数并不是很短而且在很多地方都被调用的话 ,那么将会使得可执行体的体积增大。最令人烦恼的还是当编译器拒绝内联的时候。在老 的实现中,结果很不尽人意,尽管在新的实现中有很大的改善,但是仍然还是不那么完善 的。一些编译器能够足够的聪明来指出什么函数能够内联什么不能,但是,大多数编译器 就不那么聪明了,因此这就需要我们的经验来推断。假如内联函数不能增强行能,就避免 使用它! 四. 优化你的内存使用 通常优化都有几个方面:更快的运行速度,有效的系统资源使用,更小的内存使用。

   通常情况下,代码优化都是试图在以上各个方面进行改善。重新放置声明技术被证明是消 除多余对象的建立与销毁,这样既减小了程序的大小又加快了运行速度。然而其他的优化 技术都是基于一个方面------更快的速度或者者是更小的内存使用。有的时候,这些目标是互斥 的,压缩了内存的使用往往却减慢了代码速度,快速的代码却又需要更多的内存支持。下 面总结两种在内存使用上的优化方法:

   1. Bit Fields 在 C/C++中都能够存取与访问数据的最小构成单元:bit。由于bit 并不是 C/C++基本的 存取单元,因此这里是通过牺牲运行速度来减少内存与辅助存储器的空间的使用。注意:

   一些硬件结构可能提供了特殊的处理器指令来存取 bit,因此 bit fields 是否影响程序的速 度取决于具体平台。

   在我们的现实生活中,一个数据的许多位都被浪费了,由于某些应用根本就不可能有那 么大的数据范围。也许你会说,bit 是如此之小,通过它就能减小存储空间的使用吗?的确 ,在数据量很小的情况下不可能看出什么效果,但是在数据量惊人的情况下,它所节约的空 间还是能够让我们的眼睛为之一亮的。也许你又会说,现在内存与硬盘越来越便宜,何苦 要费半天劲,这省不了几个钱。但是还有另外一个原因一定会使你信服,那就是数字信息 传输。一个分布式数据库都会在不一致的地点有多份拷贝。那么数百万的纪录传输就会显得 十分昂贵。Ok,现在我们就来看看该如何做吧,首先看下面这段代码:

   struct BillingRec { long 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 cust_id: 24; // 23 bits + 1 sign bit int timestamp: 24; enum CallType {//... }; enum CallTariff {//... }; unsigned call: 3; unsigned tariff: 2; }; 现在一个数据从 16 字节缩减到了 8 字节,减少了一半,怎么样,效果还是显著的吧:) 2. Unions Unions 通过把两个或者更多的数据成员放置在相同地址的内存中来减少内存浪费,这就 要求在任何时间只能有一个数据成员有效。Union 能够有成员函数,包含构造函数与析构 函数,但是它不能有虚函数。C++支持 anonymous unions。anonymous union 是一个未命名 类型的未命名对象。比如:

   union { long n; void * p}; // anonymous n = 1000L; // members are directly accessed p = 0; // n is now also 0 不像命名的 union,它不能有成员函数与非 public 的数据成员。

   那么 unions 什么时候是有用的呢?下面这个类从数据库中获取一个人的信息。关键字既可 以是一个特有的 ID 或者者人名,但是二者却不能同时有效:

   class PersonalDetails { private: char * name; long ID; //... public: PersonalDetails(const char *nm); //key is of type char * used PersonalDetails(long id) : ID(id) {} //numeric key used }; 上面这段代码中就会造成内存的浪费,由于在一个时间只能有一个关键字有效。anonymous union 能够在这里使用来减少内存的使用,比如:

   class PersonalDetails { private: union //anonymous { char * name; long ID; }; public: PersonalDetails(const char *nm); PersonalDetails(long id) : ID(id) {/**/} //...

推荐访问: 代码优化经验总结 标签 优化 代码 c语言代码优化

本文来源:https://www.windowchina.cn/fanwendaquan/gongwenfanwen/11196.html

推荐内容