循环优化

来源:互联网 发布:根据json生成实体类 编辑:程序博客网 时间:2024/05/18 03:42

循环优化

在大多数程序中,循环是一种常见的结构。由于大多数执行时间都耗费在循环上,因此有必要对循环做优化。

循环中止

循环中止条件要小心使用,否则会产生错误。一般有:

  • 经常写递减到0的循环,使用简单的条件。

  • 经常使用一个无符号整型的计数器,然后测试是否等于0。

显示了两个例子实现了计算 n! ,两个例子都显示了循环终止。第一个实现使用递增循环计算 n! ,而第二个实现使用递减循环计算 n!

表 4.1. C 递增和递减循环的C代码

递增循环递减循环
int fact1(int n){    int i, fact = 1;    for (i = 1; i <= n; i++)        fact *= i;    return (fact);}
int fact2(int n){    unsigned int i, fact = 1;    for (i = n; i != 0; i--)        fact *= i;    return (fact);}

 

 4.2 显示了以上两个例子经过编译器编译之后产生汇编代码,C 代码都使用编译器选项 -O2 -Otime

表 4.2. C 递增和递减循环的汇编代码

递增循环递减循环
fact1 PROC    MOV      r2, r0    MOV      r0, #1    CMP      r2, #1    MOV      r1, r0    BXLT     lr|L1.20|    MUL      r0, r1, r0    ADD      r1, r1, #1    CMP      r1, r2    BLE      |L1.20|    BX       lr    ENDP
fact2 PROC    MOVS     r1, r0    MOV      r0, #1    BXEQ     lr|L1.12|    MUL      r0, r1, r0    SUBS     r1, r1, #1    BNE      |L1.12|    BX       lrENDP

比较 表 4.2 显示了在递增循环汇编代码中的 ADD/CMP 指令对在递减循环汇编代码中被替换成了一个单指令 SUBS 。这是由于与0比较可以进行优化。

为了保存循环中的一条指令,变量 n 在循环中不需要保存,因此变量的用法在递减汇编代码也要保存。这使得容易分配寄存器。

在初始化循环计数器时一般将其设为需要重复的次数,然后递减到0。这也同样适合于while 和 do while 语句。

循环展开

对于一些比较小的循环可以被展开以获得更高的执行速度,但可能代码长度增加。当循环被展开后,循环计数器值的刷新次数较少且跳转也较少。如果循环次数较少,完全可以展开,循环的开消几乎没有。ARM 编译器可在选中 -O3 -Otime 时自动展开循环。否则,需要源代码里展开。

注意

手动展开循环可能隐藏了循环的特点使得编充程序不能识别循环而没有对其进行循环的其它优化。

展开循环的好处与坏处都在 表 4.3 中有显示。两个程序通过移位后测试最低位以此来计算位数。

第一种方法是用一个循环计算位数。第二种方法将循环展开,一次循环计算4次,因此 n 每次移4位,减少了循环次数。这样编译器可以更好的优化。

Table 4.3. 展开和非展开计算位数的C 代码

位计算循环展开的位计算循环
int countbit1(unsigned int n){    int bits = 0;    while (n != 0)    {        if (n & 1) bits++;        n >>= 1;    }    return bits;}
int countbit2(unsigned int n){    int bits = 0;    while (n != 0)    {        if (n & 1) bits++;        if (n & 2) bits++;        if (n & 4) bits++;        if (n & 8) bits++;        n >>= 4;    }    return bits;}

 4.4 显示了 表 4.3 中例子通过编译器产生的汇编代码,这些C代码在编译时要使用选项 -O2

Table 4.4. 展开和非展开计算位数的汇编代码

位计算循环展开的位计算循环
countbit1 PROC    MOV      r1, #0    B        |L1.20||L1.8|    TST      r0, #1    ADDNE    r1, r1, #1    LSR      r0, r0, #1|L1.20|    CMP      r0, #0    BNE      |L1.8|    MOV      r0, r1    BX       lrENDP
countbit2 PROC    MOV      r1, r0    MOV      r0, #0    B        |L1.48||L1.12|    TST      r1, #1    ADDNE    r0, r0, #1    TST      r1, #2    ADDNE    r0, r0, #1    TST      r1, #4    ADDNE    r0, r0, #1    TST      r1, #8    ADDNE    r0, r0, #1    LSR      r1, r1, #4|L1.48|    CMP      r1, #0    BNE      |L1.12|    BX       lrENDP

对于ARM7,在最左边的那种实现方法的汇编代码中检测一位需要6个 周期。但它的代码仅有9条指令。而右边的那种展开循环的方法,每次循环检测4位,平均每位只需3周期。它的代价是较大的代码--15条指令。

原创粉丝点击