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,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
//常量数组的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,-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
:
//常量数组的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的注释).而对于两种实现方法,保存常量的空间并没有发生任何变化.
五. 结束语
优化永无至境,但是一开始采用好的编码方法将获得更高的效率,降低不必要的工作和性能开销.
在此抛砖引玉,希望有更多的人在编写代码的时候获得一些灵感和方法,写出更加高效和精简的代码.
有一句话共勉: 好的设计不是无法再添加代码,而是再也没有办法减少代码