c++程序的效率优化初涉

能写出稳定高效的程序一直是程序员所追求的,今天就和大家一起探讨一下关于C++程序优化的几点看法。

由于C/C++语言的复杂性,致使C++编译器隐藏了层层幔布,我们不经意的一条语句都可能是编译器幕后几经周折的结果,在要求程序高效运行的环境下,每一条语句都会让我们慎之又慎,而程序优化又是个十分广泛的话题,包括程序架构设计的优化,语言本身的优化,编程技巧和策略等等,如此大的范围非我能力所及,这里谈的优化就是在实际开发中遇到的问题。

一.  举手之劳的小差别

既然说优化就一定要仔细,不放过任何微小的细节,先看个小例子

1.  int a, i = 5;

a = i>>1;  (1)

i = 5;

a = i/2;     (2)

语句(1)和语句(2)都是将i的数值除2取整,但实现方式却存在较大的差别。

(1)  a = i>>1  相当于

sar        eax, 1      //算术右移一位

(2)  a = i/2     相当于

cdq                       //将EDX所有值设置为EAX高位的值

sub       eax, edx

sar        eax, 1

可以看到语句(2)完成同样功能却比语句(1)多出两条指令。需要注意的是,当被除数i是正数时,两者数值相同,因两种写法均向上取整,当i为负数时却不一样,因为语句(2)向上取整,即 i=-5时,语句(2) a=-2而语句(1) a=-3。

正所谓勿以善小而不为,勿以恶小而为之啊,优化是大局观,但也别忽视小问题。

二.  猜猜哪一个更快

写法(1)

int a[] = {1, 2, 100, 256};

for(int i=0; i<sizeof a/sizeof a[0]; i++)

if(a[i]==50)

{

printf("for 匹配到50!/n");

}

写法(2)                                                           写法(3)

int  n = 50;                                                    int  n = 50;

switch(n)                                                       switch(n)

{                                                                   {

case 1:                                                          case 1:

printf("switch匹配到1!/n");                                 printf("switch匹配到1!/n");

break;                                                           break;

case 2:                                                          case 2:

printf("switch匹配到2!/n");                                 printf("switch匹配到2!/n");

break;                                                           break;

case 100:                                                      case 100:

printf("switch匹配到100!/n");                             printf("switch匹配到100!/n");

break;                                                           break;

case 256:                                                     case 255:

printf("switch匹配到256!/n");                             printf("switch匹配到255!/n");

break;                                                           break;

default:                                                       default:

break;                                                           break;

}                                                                   }

大家可以先不看结论,自己思考一下看哪个执行效率最高,为什么这样认为,什么原因导致的。

好了,让我们看一看,凭感觉写法(1)应该最慢,因为是逐个比较。写法(2)和写法(3)实在是看不出有任何差别,效率应该是一样的,所以写法(1)最慢,写法(2)和写法(3)效率一样,等一等,真的是这样吗?我们反汇编看一下(注:本文举例程序均在VC6.0下编译)。

写法(2)反汇编后类似如下代码:

switch(n)

{

004030F4  mov         eax,dword ptr [ebp-8]                 // [ebp-8] =50

004030F7  mov         dword ptr [ebp-20h],eax             // [ebp-20h] = 50

004030FA  cmp         dword ptr [ebp-20h],64h           // 与分支100进行比较

004030FE  jg             00403114                                    // 若大于100,跳向00403114处

00403100  cmp         dword ptr [ebp-20h], 64h           // 与分支100进行比较

00403104  je             00403151                                    //跳转到分支100处理程序处

00403106  cmp         dword ptr [ebp-20h],1               //与分支1进行比较

0040310A  je             0040311f                                   //跳转到分支1处理程序处

0040310C  cmp         dword ptr [ebp-20h],2              //与分支2进行比较

00403110  je             00403138                                   //跳转到分支2处理程序处

00403112  jmp          00403181                                  //跳出switch

00403114  cmp         dword ptr [ebp-20h],100h        //与分支256进行比较

0040311B  je            0040316a                                   //跳转到分支256处理程序处

0040311D  jmp         00403181                                 //跳处switch

case 1:

printf("switch跳转到1!/n");

0040311F   mov         esi,esp

00403121   push        offset string "switch/xcc/xf8/xd7/xaa/xb5/xbd1!/n" (004161e0)

00403126   call         dword ptr [__imp__printf (0041849c)]

0040312C   add         esp,4

0040312F   cmp         esi,esp

00403131   call        _chkesp (00402e5a)break;

break;

004015EB   jmp         00403181  //跳出switch

case 2:

printf("switch跳转到2!/n");  //  与上面代码类似故省略.

….

}                                          // switch  结束

00403181: …

看到这里相信大家已经知道了,写法(1)最慢的结论并不一定正确,是有条件的,因为写法(2)同样是逐个比较,只是比较的顺序做了调整,先与分支中第二大的数做比较,在比较不成功时减少了比较次数,在比较成功的情况下二者在效率上是相似的。那写法(3)是否也一样呢?接着看,写法(3)反汇编后代码如下:

switch(n)

{

004030F4   mov        eax,dword ptr [ebp-8]            // [ebp-8] =50

004030F7   mov        dword ptr [ebp-20h],eax        //[ebp-20h]=50

004030FA   mov        ecx,dword ptr [ebp-20h]       //ecx = 50

004030FD   sub        ecx,1                                        //ecx =49

00403100   mov        dword ptr [ebp-20h],ecx         //[ebp-20h]=49

00403103   cmp        dword ptr [ebp-20h],0FEh     //与254相比较

0040310A   ja          00403180                                 //跳出switch

0040310C   mov        eax,dword ptr [ebp-20h]        //eax = 49

0040310F   xor         edx,edx                                     // edx清零

00403111   mov         dl,byte ptr  (004031fc)[eax]  // dl = [004031fc+eax]

00403117   jmp         dword ptr [edx*4+4031E8h] // 跳向[edx*4+4031E8h]

case 1:

printf("switch跳转到1!/n");

0040311E   mov        esi,esp

00403120   push        offset string "switch/xcc/xf8/xd7/xaa/xb5/xbd1!/n" (004161e0)

00403125   call         dword ptr [__imp__printf (0041849c)]

0040312B   add         esp,4

0040312E   cmp         esi,esp

00403130   call        _chkesp (00402e5a)

break;

00403135   jmp         $L94705+17h (00403180)

case 2:

}

00403181,.

可以看到写法(3)的switch实现方式与写法(2)截然不同。

请大家注意这样几个地方,sub  exc, 1,执行后ecx = 49,然后与254相比较,大于则跳出switch,否则跳向[edx*4+4031E8h]处,可以看出254是分界点,那么edx里是什么以及[edx*4+4031E8h]是何值呢,看下图004031FC处数据。

(四 暂略)

这里并非鼓励将for改为switch,而是应该根据程序运行环境做适当的选择。我们接下来再看一个有趣的例子(1)和(2)哪一个效率要高一些呢?

for(int i=0; i<200000; i++)            for(int i=0; i<100; i++)

for(int j=0; j<100; j++)                 for(int j=0; j<200000; j++)

{                                                   {

DoSomething();                            DoSomething();

}                                                   }

(1)                                                 (2)

单纯看这个问题和鸡生蛋还是蛋生鸡一样令人不好回答,但实际上(1)要比(2)效率低,虽然二者在反汇编代码中仅循环计数(100,20000互相颠倒)不同。

一般,我们遵从这样一条经验"在多重嵌套的循环中,如果有可能,应将最长的循环放到最内层,最短的循环放到最外层,这样可以减少CPU跨切循环层的次数,从而优化程序的性能"(注1)。类似的问题还有while和for,

while(1)                                                                      for(;;)

{                                                                                {

int i = 0;                                                                     int i = 0;

}                                                                                }

00402C48  mov ax, 1                                                00402C5A  mov dword ptr[i], 0

00402C4D  test ax, ax                                              00402C61  jmp  00402C5A

00402C4F  je     00402C5A  //跳出while

00402C51  mov dword ptr[i], 0

00402C58  jmp  00402C48

答案已经很明显了,while不但指令多,而且占有用寄存器,增加了判断跳转,所以这种情况下for语句要好。类似例子还有,限于篇幅,此处不再一一举例。

三.  减小尺寸,提高效率

大家先看这个结构

typedef  struct_Test

{

char  c_1;

int     v_1;

char  c_2;

int     v_2;

char  c_3;

}Test, *PTest;

(1)

计算一下sizeof(Test)=?,这太简单了,只是加法吗!sizeof(Test)等于11,可惜不是,你的VC编译器会计算出sizeof(Test)=20,不相等的原因是编译器对内存对齐处理造成的,缺省情况下,C/C++编译器默认将结构、栈中的成员数据进行内存对齐。内存对齐的原因是为了提高程序的性能,大部分资料都会提到两点原因,

1.  平台原因:某些硬件平台只能在某些地址处去某些特定类型的数据,否则抛出异常。

2.  性能原因:为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存仅需要做一次访问。

好了,原因已经找到了,怎么办呢?

大家一定记得这样一条预编译指令#pragma pack(n),用来改变字节对齐方式,我们只要在程序中加上一句#pragma pack(1)就可以了,这样计算sizeof(Test)就是11了。可这样我们写程序的效率就要降低了,还可能带来平台性的问题,有一个简单的方法可以寻求平衡。

typedef  struct_Test 1

{

char  c_1;

char  c_2;

char  c_3;

int     v_1;

int     v_2;

}Test1, *PTest1;

这样减少了数据尺对又不失效率,sizeof(Test1)等于12。

当我们自定义的结构过于复杂时很有必要思考一下数据的对齐方式以此来优化程序的效率

四.  谁耗费了我的效率

Class Animal                           class Bird: public Animal

{                                             {

public:                                     public:

Animal();                                 Bird();

~Animal();                               ~Bird();

public:                                     public:

string name, id;                      string Bird_name, Bird_id;

}                                             }

有这样一个函数:Bird ReturnBird(Bird b){return b;},如果我们调用这样一个什么对哦偶没有做的函数,会有什么事情发生呢?

Bird  parrot;

ReturnBird(parrot);

整体上看Bird的拷贝构造函数会被调用,用parrot初始化b,接着拷贝构造函数再被调用一次,以b初始化函数的返回值,接着b的析构函数被调用,针对返回值的析构函数也被调用。所以这个什么都没做过的函数耗费的成本是2次拷贝构造函数,2次析构函数。但我们的问题还远没有结束,编译器依然在幕后勤奋着呢,因为Bird含有两个string对象,所以每次构造Bird时,同时也要构造两个string对象,而Bird继承自Animal,所以构造Bird对象的同时必须构造Animal对象,而Animal对象也含有两个string对象,所以构造一个Animal对象也要构造两个string对象。

好了,我们已经看到,将Bird对象以传值方式调用该函数,会导致一次Bird的拷贝构造,一个Animal的构造函数(注2)及4次string的构造函数。当Bird的副本被销毁时,其析构函数依次被调用,所以一次Bird对象的产生到结束耗费6个构造函数和6个析构函数。

该例子中Bird对象一共产生两次,一次用于参数,一次用于返回值。所以这个函数的成本是12次构造函数,12次析构函数,这个看似无害的函数在幕后却存在如此多的操作。

摆脱这样的低效方法是按引用的方式传入参数而非传值方式

Bird&  ReturnBird(const Bird& b); 这种方式效率会高很多,没有任何对象产生,没有构造析构的调用开销

五.  适当声明,注意初始化

先看不当的声明给我们带来的问题,下面两个函数完成同样功能

void  Test1(bool bTest)                                 void  Test2(bool bTest)

{                                                                   {

Bird  parrot;                                                  if(bTest){

if(bTest){                                                           Bird parrot;

...  // 操作 parrot                                              ...  // 操作  parrot

}                                                                   }

return;                                                          return;

}                                                                   }

(写法1)                                                          (写法2)

当Test1,Test2函数被不断调用且bTest = false时,写法2的效率要明显高于写法1。

对这两个函数各自执行10000000次可知,即便在什么都不做的情况下,两种写法仍然有近似4s的效率之差。所以,类对象的声明应该尽量推迟,确定用到时再声明。

文章出处:http://hi.baidu.com/upcal/blog/item/c0c6a9ef8d850d14fdfa3cc5.html

c++程序的效率优化初涉,布布扣,bubuko.com

时间: 2024-10-23 12:01:40

c++程序的效率优化初涉的相关文章

php程序效率优化的一些策略小结

php程序效率优化的一些策略小结 1.在可以用file_get_contents替代file.fopen.feof.fgets等系列方法的情况下,尽量用 file_get_contents,因为他的效率高得多!但是要注意file_get_contents在打开一个URL文件时候的PHP版本问题; 2.尽量的少进行文件操作,虽然PHP的文件操作效率也不低的; 3.优化Select SQL语句,在可能的情况下尽量少的进行Insert.Update操作(在update上,我被恶批过); 4.尽可能的使

(转)as3效率优化

1.改进算法无论对于那一种程序,好的算法总是非常重要的,而且能够极大地提高程序性能,所以任何性能的优化第一步就是从算法或者说程序逻辑的优化开始,检查自己的程序是否有多余的运算,是否在没有必要的时候做了无用功,往往从这些方面就能找到那些导致性能低下的地方. 2.优化细节代码针对细节总是好的,有一些小技巧比如:用 var obj:Object = {}; 要比 var obj:Object = new Object();要好:var arr:Array = []; 要比 var arr:Array

游戏效率优化(2) 使用const关键字

游戏效率优化(2)使用const关键字 DionysosLai  2014-5-15 使用const有很多好处,比方保护被修饰的东西,防止意外修改,提高程序健壮性等作用.不过使用const 可以提高程序运行效率,却很少有人知道. 下面看几个例子,在看cocos2d源码时,我们经常会看到如下类似的代码: ccpAdd(const CCPoint& v1, const CCPoint& v2) { return v1 + v2; } ccpSub(const CCPoint& v1,

(转)对《30个提高Web程序执行效率的好经验》的理解

阅读了博客园发布的IT文章<30个提高Web程序执行效率的好经验>,这30条准则对我们web开发是非常有用的,不过大家可能对其中的一些准则是知其然而不知其所以然. 下面是我对这些准则的理解和分析,有些有关JS性能的准则,我也测试了它们的差异,大家可以下载DEMO页面,如有理解不正确的地方,请大家指正.也非常欢迎大家补充. 测试环境: OS:Vista; Processor:3.40GHz; Memory: 2.00GB; System type: 32-bit Operating System

漫游Kafka设计篇之效率优化

原文地址:http://blog.csdn.net/honglei915/article/details/37564757 Kafka在提高效率方面做了很大努力.Kafka的一个主要使用场景是处理网站活动日志,吞吐量是非常大的,每个页面都会产生好多次写操作.读方面,假设每个消息只被消费一次,读的量的也是很大的,Kafka也尽量使读的操作更轻量化. 我们之前讨论了磁盘的性能问题,线性读写的情况下影响磁盘性能问题大约有两个方面:太多的琐碎的I/O操作和太多的字节拷贝.I/O问题发生在客户端和服务端之

ABAP程序编写过程中怎么提高程序执行效率一

影响ABAP程序的运行效率主要是在程序中大量数据的取得,如果取数不得方法,很影响报表的运行效率,所有优化ABAP程序主要是优化数据取数的方法.下面这几点可以有效的提高取数的效率,从而来提高程序的运行效率. 1.选择最有效率的表名顺序(只在基于规则的优化器中有效): ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 drivingtable)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表.如果有3个以上的表连

(同事的原创)关于效率优化的一点工作心得

文是单位同事胡计平的一个关于效率优化的总结,内容很实用,转贴到blog里,以备自己日后查看,也希望能对更多的人有所帮助 最近写一程序,跟效率优化打上了交道,把其中的体会写下来,供大家讨论分享,我想效率优化工作可以分为如下几个步骤: (1)查找影响效率的瓶颈之处:定位的方法当然是使用时间函数,一般精确的使用GetTickCount就可以,非常精确的使用 function GetCycleCount: Int64;asm  RDTSC;    //得到当前CPU的时钟周期数.end; 想必这个知识大

Unity3d代码及效率优化总结

1.PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU. 2.如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unlit目录下的.它们更高效. 3.尽可能共用材质. 4.将不需要移动的物体设为Static,让引擎可以进行其批处理. 5.尽可能不用灯光. 6.动态灯光更加不要了. 7.尝试用压缩贴图格式,或用16位代替32位. 8.如果不需要别用雾效(fog) 9.尝试用OcclusionCulling,在房间过道多

as3效率优化

1.改进算法无论对于那一种程序,好的算法总是非常重要的,而且能够极大地提高程序性能,所以任何性能的优化第一步就是从算法或者说程序逻辑的优化开始,检查自己的程序是否有多余的运算,是否在没有必要的时候做了无用功,往往从这些方面就能找到那些导致性能低下的地方. 2.优化细节代码针对细节总是好的,有一些小技巧比如:用 var obj:Object = {}; 要比 var obj:Object = new Object();要好:var arr:Array = []; 要比 var arr:Array