5.8 编译器对循环结构的优化(1)
5.7节介绍了3种循环结构,在执行效率上,do循环结构是最高的。由于do循环在结构上非常精简,利用了程序执行时由低地址到高地址的特点,只使用一个条件跳转指令就完成了循环,因此已经无需在结构上进行优化处理。
由于循环结构中也有分支功能,所以4.4.2节介绍的分支优化同样适用于循环结构。分支优化会使用目标分支缓冲器,预读指令。由于do循环是先执行后比较,因此执行代码都在比较之前,如下所示。
- int i = 0;
- 00401248 mov dword ptr [ebp-4],0
- do
- {
- i++;
- 0040124F mov eax,dword ptr [ebp-4]
- 00401252 add eax,1
- 00401255 mov dword ptr [ebp-4],eax
- printf("%d", i);
- ; printf讲解略
- } while(i < 1000);
- ; 此处的汇编代码在退出循环时才预测失败
- 00401269 cmp dword ptr [ebp-4],3E8h
- 00401270 jl main+1Fh (0040124f)
do循环结构中只使用了一次跳转就完成了循环功能,大大提升了程序的执行效率。因此,在三种循环结构中,它的执行效率最高。
while循环结构的效率要比do循环结构低。while循环结构先比较再循环,因此无法利用程序执行顺序来完成循环。同时,while循环结构使用了2个跳转指令,在程序流程上就弱于do循环结构。为了提升while循环结构的效率,可以将其转成效率较高的do循环结构。在不能直接转换成do循环结构的情况下,可以使用if单分支结构,将do循环结构嵌套在if语句块内,由if语句判定是否能执行循环体。因此,所有的while循环都可以转换成do循环结构,如图5-13所示。
|
| 图5-13 while循环结构的优化图 |
图5-13为代码清单5-23使用O2选项后编译的Release版结构流程图,该图截取自IDA。图5-13划分了程序的流程,箭头方向显示,反汇编代码中有一个单分支结构与循环结构。首先由条件跳转指令jl比较参数,小于等于0则跳转。可见这是一个if语句。
如果jl跳转失败,则顺序向下执行,进入标号loc_40100C处。这是一个循环语句块。此语句块内使用条件跳转指令jle,当ecx小于等于edx时,跳转到地址标号loc_40100C处。edx中保存参数数据,ecx每次加1,使eax每次对ecx累加。先执行,后判断,有了这个特性便可将图5-13所对应的代码还原成由单分支结构中嵌套do循环结构的高级代码。转换成对应的C++(www.cppentry.com)代码如下:
- int LoopWhile(int nCount){
- int nSum = 0;
- int nIndex = 0;
- if(nCount >= 0){
- do{
- nSum += nIndex;
- nIndex++;
- }while(nIndex <= nCount)
- }
- return nSum;
- }
经过转换后,代码的功能没有任何改变,只是在结构上有了调整,变成了单分支结构加do循环结构。
以上讨论了while循环结构的优化,可以将其转换为do循环结构来提升效率。
从结构特征上可知,for循环是执行速度最慢的,它需要三个跳转指令才能够完成循环,因此也需要对其进行优化。for循环可以这么转换吗?从循环结构上看,其结构特征和while循环结构类似。由于赋初值部分不属于循环体,可以忽略。只要将比较部分放到循环体内,即是一个while循环结构。既然可以转换while循环结构,那么自然可以转换为do循环结构进行优化以提升效率。
有了for循环结构的优化方案,那么在对其优化过程中,VC++(www.cppentry.com) 6.0能否按照此方案进行优化呢?将代码清单5-24使用O2选项进行重新编译,优化后的for循环反汇编代码如代码清单5-25所示。
代码清单5-25 for循环结构—Release版
- .text:00401000 sub_401000 proc near
- ; 函数参数标号定义arg_0
- .text:00401000 arg_0 = dword ptr 4
- ; 使用edx保存参数arg_0
- .text:00401000 mov edx, [esp+arg_0]
- ; 清空eax,ecx
- .text:00401004 xor eax, eax
- .text:00401006 xor ecx, ecx
- .text:00401008 test edx, edx
- ; 检查edx,小于0则跳转到标号short locret_401013处,该函数结束
- .text:0040100A jl short locret_401013
- ; 说明此处标号在地址sub_401000+0x11处被调用
- .text:0040100C loc_40100C:
- ; 执行eax加等于ecx操作
- .text:0040100C add eax, ecx
- ; 执行ecx自加1操作
- .text:0040100E inc ecx
- .text:0040100F cmp ecx, edx
- ; 比较ecx与edx,小于等于则跳转到标号short loc_40100C处,这是一个向上跳
- .text:00401011 jle short loc_40100C
- ; 函数返回地址标号locret_401013处,被地址标号sub_401000+0x0A处调用
- .text:00401013 locret_401013:
- .text:00401013 retn