一段典型代码的效率剖析

一.  起源

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

二.  原始代码(下面简称代码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的第一个参数是一个常量指针,实际情况也是如此).而且对代码并没有做什么改动,只是修改了一个数组的定义方法,来达到优化的目的.

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

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

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

8001e348 <InintExchangeRateSet>:

8001e348:     27bdff90      addiu      sp,sp,-112            //耗费更多的堆栈空间

8001e34c:     3c028027    lui    v0,0x8027

8001e350:     24423618    addiu      v0,v0,13848         //数组的访问地址:0x80273618

8001e354:     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,16

8001e38c:     27a40010    addiu      a0,sp,16        //参数1

8001e390:     24050cee     li      a1,3310               //参数2

8001e394:     00003021    move      a2,zero          //参数3

8001e398:     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     //参数4

8001e3b0:     8fbf0068      lw    ra,104(sp)

8001e3b4:     03e00008    jr     ra

8001e3b8:     27bd0070    addiu      sp,sp,112

代码A访问的数组资源:       //常量数组的ROM保存地址(不包括黑体部分)

80273610:    00020063 00000000 00000000 3ff00000     c..............?

80273620:    0e560419 401bb22d f5c28f5c 403e5c28     ..V.-..@/...(/>@

80273630:    66666666 405a8666 353f7cee 401f3a5e     fffff.Z@.|?5^:.@

80273640:    3dd97f63 3fe05879 76c8b439 3ff09fbe     c..=yX.?9..v...?

80273650:    f9db22d1 3ff26a7e 3e425aee 3ff5d9e8     ."..~j.?.ZB>...?

80273660:    020c49ba 3fe4872b 5e353f7d 3ff049ba     .I..+..?}?5^.I.?

80273670:    00000001 00000002 00000002 00000004     ................

80273680:    00000001 00000002 00000003 00000004     ................

80273690:    00000006 00000008 0000000c 00000010     ................

802736a0:     00000018 00000020 00000000 00000000     .... ...........

 

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

8001e348 <InintExchangeRateSet>:

8001e348:     27bdffe8      addiu      sp,sp,-24

8001e34c:     3c048027    lui    a0,0x8027

8001e350:     248435c8    addiu      a0,a0,13768  //数组的访问地址: 0x802735c8,

//直接传递给函数的第一个参数a0

8001e354:     24050cee     li      a1,3310        //参数2

8001e358:     00003021    move      a2,zero   //参数3

8001e35c:     afbf0010      sw   ra,16(sp)      

8001e360:     0c00a256     jal    80028958 <SetSystemResouce>

8001e364:     24070058    li      a3,88            //参数4

8001e368:     8fbf0010      lw    ra,16(sp)

8001e36c:     03e00008    jr     ra

8001e370:     27bd0018    addiu      sp,sp,24

代码A访问的数组资源:

802735c8 <ExchangeRateTab>:       //常量数组的ROM保存地址

802735c8:     00000000 3ff00000 0e560419 401bb22d     .......?..V.-..@

802735d8:    f5c28f5c 403e5c28 66666666 405a8666     /...(/>@fffff.Z@

802735e8:     353f7cee 401f3a5e 3dd97f63 3fe05879     .|?5^:.@c..=yX.?

802735f8:     76c8b439 3ff09fbe f9db22d1 3ff26a7e     9..v...?."..~j.?

80273608:    3e425aee 3ff5d9e8 020c49ba 3fe4872b     .ZB>...?.I..+..?

80273618:    5e353f7d 3ff049ba 00000001 00000002     }?5^.I.?........

80273628:    00000002 00000004 00000001 00000002     ................

80273638:    00000003 00000004 00000006 00000008     ................

80273648:    0000000c 00000010 00000018 00000020     ............ ...

 

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

 

五.  结束语

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

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

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

1、选择合适的算法和数据结构 2、使用尽量小的数据类型 3、减少运算的强度 (1)查表 (2)求余运算 (3)平方运算 (4)用移位实现乘除法运算 (5)避免不必要的整数除法 (6)使用增量和减量操作符 (7)使用复合赋值表达式 (8)提取公共的子表达式 4、结构体成员的布局 (1)按数据类型的长度排序 (2)把结构体填充成最长类型长度的整倍数 (3)按数据类型的长度排序本地变量 (4)把频繁使用的指针型参数拷贝到本地变量 5、循环优化 (1)充分分解小的循环 (2)提取公共部分 (3)延时函数 (4)while循环和do…while循环 (5)循环展开 (6)循环嵌套 (7)Switch语句中根据发生频率来进行case排序 (8)将大的switch语句转为嵌套switch语句 (9)循环转置 (10)公用代码块 (11)提升循环的性能 (12)选择好的无限循环 6、提高CPU的并行性 (1)使用并行代码 (2)避免没有必要的读写依赖 7、循环不变计算 8、函数 (1)Inline函数 (2)不定义不使用的返回值 (3)减少函数调用参数 (4)所有函数都应该有原型定义 (5)尽可能使用常量(const) (6)把本地函数声明为静态的(static) 9、采用递归 10、变量 (1)register变量 (2)同时声明多个变量优于单独声明变量 (3)短变量名优于长变量名,应尽量使变量名短一点 (4)在循环开始前声明变量 11、使用嵌套的if结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值