(九)JVM之循環優化

循環無關代碼(Loop-invariant Code)外提

如下循環代碼: 

package per.william.ex.simd;

public class Foo {

     int foo(int x,int y ,int[] a){

        int sum=0;

        for(int i=0;i<a.length;i++){

            sum+=x*y+a[i];
        }
        return sum;
    }
}

查看循環體對應的字節碼:

 int foo(int, int, int[]);
    descriptor: (II[I)I
    flags: (0x0000)
    Code:
      stack=4, locals=6, args_size=4
         0: iconst_0
         1: istore        4
         3: iconst_0
         4: istore        5
         //循環體開始
         6: iload         5  // load i 
         8: aload_3          // load a[]
         9: arraylength      // a.length
        10: if_icmpge     32 // i<a.length
        13: iload         4  // load sum
        15: iload_1          // load x
        16: iload_2          // load y
        17: imul             // x*y 
        18: aload_3          // load a[]
        19: iload         5  // load i 
        21: iaload           // load a[i]
        22: iadd             // x*y+a[i]
        23: iadd             // x*y+a[i]+sum
        24: istore        4  // sum=sum+x*y+a[i]
        26: iinc          5, 1 //i++
        29: goto          6  
        // 循環體結束
        32: iload         4
        34: ireturn

棧幀的局部變量表如下:

 LocalVariableTable:
        Start  Length  Slot  Name   Signature
            6      26     5     i   I
            0      35     0  this   Lper/william/ex/simd/Foo;
            0      35     1     x   I
            0      35     2     y   I
            0      35     3     a   [I
            3      32     4   sum   I

 在上面的循環體中,x*y 和 a.length 屬於循環不變代碼,即循環無關代碼。前者是個乘法運算,後者是內存訪問操作,讀取數組a 的長度(存放在數組對象的對象頭中)。理性情況,上面的循環代碼應該作如下優化:

package per.william.ex.simd;

public class Foo {

     int foo(int x,int y ,int[] a){

        int sum=0;
        int t0=x*y;
        int t1=a.length;
        for(int i=0;i<t1;i++){

            sum+=t0+a[i];
        }
        return sum;
    }
}

 不過,我通過 JITWatch 查看,貌似只是把 a.length 外提了,x*y 依舊在循環體內(不會彙編,/捂臉):

 

循環展開(Loop Unrolling)

它指減少迭代次數,在循環體中重複多次循環的迭代,例如:

int foo(int[] a) {
  int sum = 0;
  for (int i = 0; i < 64; i++) {
    sum += (i % 2 == 0) ? a[i] : -a[i];
  }
  return sum;
}

以上代碼經過一次循環展開之後將變成:

int foo(int[] a) {
  int sum = 0;
  for (int i = 0; i < 64; i += 2) { // 注意這裏的步數是 2
    sum += (i % 2 == 0) ? a[i] : -a[i];
    sum += ((i + 1) % 2 == 0) ? a[i + 1] : -a[i + 1];
  }
  return sum;
}

在 C2 中,只有計數循環(Counted Loop)才能展開:

  1. 維護一個循環計數器,並且基於計數器的出口只有一個。
  2. 循環計數器的類型只能是 int 、short 或者 char 。
  3. 每個迭代循環計數器的增量是常數。
  4. 循環計數器的上限或者下限是循環無關的數值。

循環展開有一種特殊情況,那便是完全展開(Full Unroll)。當循環的數目是固定值而且非常小時,即時編譯器會將循環全部展開。此時,原本循環中的循環判斷語句將不復存在,取而代之的是若干個順序執行的循環體。

int foo(int[] a) {
  int sum = 0;
  for (int i = 0; i < 4; i++) {
    sum += a[i];
  }
  return sum;
}

上述代碼完全展開爲:

int foo(int[] a) {
  int sum = 0;
  sum += a[0];
  sum += a[1];
  sum += a[2];
  sum += a[3];
  return sum;
}

 即時編譯器會在循環體的大小與循環展開次數之間做出權衡。例如,對於僅迭代三次(或以下)的循環,即時編譯器將進行完全展開;對於循環體 IR 節點數目超過閾值的循環,即時編譯器則不會進行任何循環展開。

 

循環判斷(Loop unswitching)外提

 

循環剝離(Loop peeling)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章