5.8 编译器对循环结构的优化(2)
分析代码清单5-25发现,它与图5-13的思路竟然完全一致。编译器通过检查,将for循环结构最终转换成了do循环结构。使用if单分支结构进行第一次执行循环体的判断,再将转换后的do循环嵌套在if语句中,就形成了“先执行,后判断”的do循环结构。由于在O2选项下,while循环及for循环都可以使用do循环进行优化,所以在分析经过O2选项优化的反汇编代码时,很难转换回相同源码,只能尽量还原等价源码。读者可根据个人习惯转换对应的循环结构。
从结构上优化循环后,还需从细节上再次优化,以进一步提高循环的效率。4.4节介绍了编译器的各种优化技巧,循环结构的优化也使用这些技巧,其中常见的优化方式是“代码外提”。例如,循环结构中经常有重复的操作,在对循环结构中语句块的执行结果没有任何影响的情况下,可选择相同代码外提,以减少循环语句块中的执行代码,提升循环执行效率,如代码清单5-26所示。
代码清单5-26 循环结构优化—代码外提
- // C++(www.cppentry.com)源码说明:for循环完成整数累加和
- int CodePick(int nCount){
- int nSum = 0;
- int nIndex = 0;
- do {
- nSum += nIndex;
- nIndex++;
- // 此处代码每次都要判断nCount – 1,nCount并没有自减,仍然为一个固定值
- // 可在循环体外先对nCount进行减等于1操作,再进入循环体
- } while(nIndex < nCount - 1);
- return nSum;
- }
-
- // 经过优化后的反汇编代码
- .text:00401000 sub_401000 proc near; CODE XREF: _main+21-p
- .text:00401000 arg_0 = dword ptr 4
- ; 获取参数到edx中
- .text:00401000 mov edx, [esp+arg_0]
- .text:00401004 xor eax, eax
- .text:00401006 xor ecx, ecx
- ; 代码外提,对edx执行自减1操作
- .text:00401008 dec edx
- ; 进入循环体,在循环体内直接对保存参数的edx进行比较,没有任何减1操作
- .text:00401009 loc_401009: ; CODE XREF: sub_401000+Ej
- .text:00401009 add eax, ecx
- .text:0040100B inc ecx
- .text:0040100C cmp ecx, edx
- .text:0040100E jl short loc_401009
- .text:00401010 retn
- .text:00401010 sub_401000 endp
分析代码清单5-26可知,编译器将循环比较“nIndex < nCount - 1”中的“nCount – 1”进行了外提。由于“nCount – 1”中nCount在循环体中没有被修改,因此对它的操作是可以被拿到循环体外。被外提后的代码如下:
- int CodePick(int nCount){
- int nSum = 0;
- int nIndex = 0;
- nCount -= 1; // 外提代码
- do {
- nSum += nIndex;
- nIndex++;
- } while(nIndex < nCount); // 原来的nCount-1被外提了
- return nSum;
- }
这种外提是有选择性的—只有在不影响循环结果的情况下,才可以外提。
除了代码外提,还可以通过一些方法进一步提升循环结构的执行效率—强度削弱,即用等价的低强度运算替换原来代码中的高强度运算,例如,用加法代替乘法,如代码清单5-27所示。
代码清单5-27 循环强度降低优化—Release版
- // C++(www.cppentry.com)源码说明:强度削弱
- int main(int argc){
- int t = 0;
- int i = 0;
- while (t < argc){
- t = i * 99; // 强度削弱后,这里将不会使用乘法运算
- i++; // 此处转换后将为 t = i; i += 99;
- } // 利用加法运算替换掉了指令周期长的乘法运算
- printf("%d", t);
- return 0;
- }
-
- ; 优化后的反汇编代码
- .text:00401020 arg_0 = dword ptr 4
- ; 将参数信息保存到edx中
- .text:00401020 mov edx, [esp+arg_0]
- .text:00401024 xor eax, eax ; 清空eax
- .text:00401026 test edx, edx
- .text:00401028 jle short loc_401035
- .text:0040102A xor ecx, ecx ; 清空ecx
- .text:0040102C
- ; 循环语句块首地址
- .text:0040102C loc_40102C: ; CODE XREF: sub_401020+13j
- .text:0040102C mov eax, ecx ; 将ecx传入eax中
- ; ecx自加63h,即十进制99,等价于ecx每次加1乘以99
- .text:0040102E add ecx, 63h
- .text:00401031 cmp eax, edx
- .text:00401033 jl short loc_40102C ; eax小于edx则执行跳转
- .text:00401035
- .text:00401035 loc_401035: ; CODE XREF: sub_401020+8j
- ;printf函数调用处略
- .text:00401043 retn
- .text:00401043 sub_401020 endp