vlambda博客
学习文章列表

C语言代码优化示例(二)

 原创文章,欢迎分享,转载请注明来源,并给出原始链接,未经书面允许,请勿用于商业用途。

一.  起源

 在整理系统平台的代码时候,看到了一些原来同事编写的代码,有感于这些代码的编写方法和效率问题,挑出一段有代表意义的代码和大家分享如何改进代码的编码效率和提高代码的执行速率,同时降低代码对内存的消耗.

二.  原始代码(下面简称代码A)


void InintExchangeRateSet(void) { double ExchangeRateTab[] =  {      1.0,       // 美元      6.924,    //人民币      30.36,    //新台币 106.1, //日元       7.807,    //港币 0.5108, //英镑 1.039, //瑞法郎 1.151, //澳元 1.3657, //新元 0.6415, //欧元 1.018 //其它    };    SetSystemResouce((INT8U*)ExchangeRateTab, SYS_CVS_ECR, 0, YS_CVS_ECR_LEN); } 

三.  优化代码(下面简称代码B)


static const double ExchangeRateTab[] =  { 1.0, // 美元 6.924, //人民币 30.36, //新台币 106.1, //日元 7.807, //港币 0.5108, //英镑 1.039, //瑞法郎 1.151, //澳元 1.3657, //新元 0.6415, //欧元 1.018 //其它 }; void InintExchangeRateSet(void){    SetSystemResouce((INT8U*)ExchangeRateTab, SYS_CVS_ECR, 0, YS_CVS_ECR_LEN);


四.  代码解析

 

编译器使用的是mips-linux-gcc 3.3.1版本,使用O2优化.

 上面的代码非常简单,而且A和B实现的是相同的功能 (满足前提条件是SetSystemResouce的第一个参数是一个常量指针,实际情况也是如此).而且对代码并没有做什么改动,只是修改了一个数组的定义方法,来达到优化的目的.

 double ExchangeRateTab修改为static const double ExchangeRateTab

 可能大家对这种优化感到迷茫和不解: 这不是一样的吗?有什么变化?

 产生这种想法的根本原因是对编译器的行为不了解导致的,我们对比分析一下编译产生的两段代码就能立即明白这其中的道理:

 代码A编译后的汇编代码:

8001e348 <InintExchangeRateSet>:8001e348:     27bdff90      addiu     sp,sp,-112            //耗费更多的堆栈空间8001e34c:     3c028027    lui   v0,0x80278001e350:     24423618    addiu     v0,v0,13848         //数组的访问地址:0x802736188001e354:     afbf0068     sw   ra,104(sp)8001e358:     27a30010    addiu     v1,sp,16   //这部分代码是一个循环体,作用8001e35c:     24440050    addiu     a0,v0,80   //就是拷贝ROM区域的数组值到8001e360:     8c450000    lw   a1,0(v0)       //到堆栈空间.8001e364:     8c460004    lw   a2,4(v0)  //编译器在编译代码A的时候,因为数组是8001e368:     8c470008    lw   a3,8(v0) //定义在堆栈上的(使用局部变量定义), 编8001e36c:     8c48000c     lw   t0,12(v0)  //译器认为调用者是有可能修改这个数组8001e370:     ac650000     sw  a1,0(v1)     //的,而且数组包含有初值, 所以在后面的8001e374:     ac660004     sw  a2,4(v1)  // ROM区域有一个常量数据保存区.由于8001e378:     ac670008     sw  a3,8(v1)  //这个原因,它必须老老实实的使用8001e37c: ac68000c     sw       t0,12(v1) //代码把ROM区域的初值拷贝到堆栈,以便8001e380:     24420010    addiu     v0,v0,16 //应对后续代码可能对数组的修改8001e384:     1444fff6       bne v0,a0,8001e360 <InintExchangeRateSet+0x18>8001e388:     24630010    addiu     v1,v1,168001e38c:     27a40010    addiu     a0,sp,16        //参数18001e390:     24050cee     li     a1,3310    //参数28001e394:     00003021    move     a2,zero          //参数38001e398:     8c490000    lw   t1,0(v0)8001e39c:     8c4a0004     lw   t2,4(v0)8001e3a0:     ac690000     sw  t1,0(v1)8001e3a4:     ac6a0004     sw  t2,4(v1)8001e3a8:     0c00a26a     jal   800289a8 <SetSystemResouce>8001e3ac:     24070058    li     a3,88     //参数48001e3b0:     8fbf0068      lw   ra,104(sp)8001e3b4: 03e00008 jr ra8001e3b8: 27bd0070 addiu sp,sp,112代码A访问的数组资源: //常量数组的ROM保存地址(不包括黑体部分)80273610:    00020063 0000000000000000 3ff00000     c..............?80273620:    0e560419 401bb22df5c28f5c 403e5c28     ..V.-..@/...(/>@80273630:    66666666 405a8666353f7cee 401f3a5e     fffff.Z@.|?5^:.@80273640: 3dd97f63 3fe0587976c8b439 3ff09fbe c..=yX.?9..v...?80273650: f9db22d1 3ff26a7e3e425aee 3ff5d9e8 ."..~j.?.ZB>...?80273660:    020c49ba 3fe4872b5e353f7d 3ff049ba     .I..+..?}?5^.I.?80273670:    00000001 0000000200000002 00000004     ................80273680:    00000001 0000000200000003 00000004     ................80273690:    00000006 000000080000000c 00000010     ................802736a0:     00000018 0000002000000000 00000000     .... ...........

 代码B编译后的汇编代码:


8001e348 <InintExchangeRateSet>:8001e348:     27bdffe8      addiu     sp,sp,-248001e34c:     3c048027    lui   a0,0x80278001e350:     248435c8    addiu     a0,a0,13768  //数组的访问地址:0x802735c8,//直接传递给函数的第一个参数a08001e354:     24050cee     li     a1,3310        //参数28001e358:     00003021    move     a2,zero   //参数38001e35c:     afbf0010      sw  ra,16(sp)     8001e360:     0c00a256     jal   80028958 <SetSystemResouce>8001e364:     24070058    li     a3,88            //参数48001e368:     8fbf0010      lw   ra,16(sp)8001e36c:     03e00008    jr    ra8001e370:     27bd0018    addiu     sp,sp,24代码A访问的数组资源:802735c8 <ExchangeRateTab>:      //常量数组的ROM保存地址802735c8:     00000000 3ff000000e560419 401bb22d     .......?..V.-..@802735d8:    f5c28f5c 403e5c2866666666 405a8666     /...(/>@fffff.Z@802735e8:     353f7cee 401f3a5e3dd97f63 3fe05879     .|?5^:.@c..=yX.?802735f8:     76c8b439 3ff09fbef9db22d1 3ff26a7e    9..v...?."..~j.?80273608:    3e425aee 3ff5d9e8020c49ba 3fe4872b     .ZB>...?.I..+..?80273618:    5e353f7d 3ff049ba00000001 00000002     }?5^.I.?........80273628:    00000002 0000000400000001 00000002     ................80273638:    00000003 0000000400000006 00000008     ................80273648:    0000000c 0000001000000018 00000020     ............ ...


 

对比两段代码的编译结果,很明显的代码B的代码量大大缩减(由29条指令缩减到11条指令),没有一条多余的代码,同时提高了代码的执行速度(少了资源拷贝的循环体:见代码A的注释).而对于两种实现方法,保存常量的空间并没有发生任何变化.

 

五.  结束语

 

优化永无至境,但是一开始采用好的编码方法将获得更高的效率,降低不必要的工作和性能开销.

 

在此抛砖引玉,希望有更多的人在编写代码的时候获得一些灵感和方法,写出更加高效和精简的代码.

 

有一句话共勉: 好的设计不是无法再添加代码,而是再也没有办法减少代码