(九)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

查看循环体对应的字节码:

 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

栈帧的局部变量表如下:

 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

 不过,我通过 JITWatch 查看,貌似只是把 a.length 外提了,x*y 依旧在循环体内(不会汇编,/捂脸):

(九)JVM之循环优化_第1张图片

 

循环展开(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)

你可能感兴趣的:(JVM)