为什么在循环中使用字段引用之前将其复制到本地?

时间:2015-09-16 21:43:50

标签: java

这条OpenJDK行number 1455的优点是什么。

代码段:

private final char value[];
// ...
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {



        char val[] = value;      // <--- this line



        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

请注意,尽管对private final char value[]的引用被复制到本地val以便在循环内进行访问,但仍然可以通过.length访问其value字段,而不是{ {1}}。

我怀疑&#34; 表现&#34;作为答案(例如,从本地阅读比从现场阅读更快)但我会欣赏一个精确且易于阅读的答案,甚至可能有一些关于优势的数据。

1 个答案:

答案 0 :(得分:3)

我看了一下字节码,正如@user所评论的那样,它可能是一种优化,可以避免循环内的getfield调用。但是他们搞砸了,仍然在循环条件中引用了值变量...所以这实际上使得字节码越来越慢。

public int h1() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

public int h2() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + value[i];
        }
        hash = h;
    }
    return h;
}

我们可以看到两种方法产生几乎相同的字节码,但是我们的“优化”实现实际上最终使用了2次调用。

注意for循环测试(h1中的第39-45行和h2中的第37-43行)如何调用getfield来进行数组长度调用。

字节码:

public int h1();
Code:
   0: aload_0
   1: getfield      #17                 // Field hash:I
   4: istore_1
   5: iload_1
   6: ifne          53
   9: aload_0
  10: getfield      #15                 // Field value:[C
  13: arraylength
  14: ifle          53
  17: aload_0
  18: getfield      #15                 // Field value:[C
  21: astore_2
  22: iconst_0
  23: istore_3
  24: goto          39
  27: bipush        31
  29: iload_1
  30: imul
  31: aload_2
  32: iload_3
  33: caload
  34: iadd
  35: istore_1
  36: iinc          3, 1
  39: iload_3
  40: aload_0
  41: getfield      #15                 // Field value:[C
  44: arraylength
  45: if_icmplt     27
  48: aload_0
  49: iload_1
  50: putfield      #17                 // Field hash:I
  53: iload_1
  54: ireturn

public int h2();
Code:
   0: aload_0
   1: getfield      #17                 // Field hash:I
   4: istore_1
   5: iload_1
   6: ifne          51
   9: aload_0
  10: getfield      #15                 // Field value:[C
  13: arraylength
  14: ifle          51
  17: iconst_0
  18: istore_2
  19: goto          37
  22: bipush        31
  24: iload_1
  25: imul
  26: aload_0
  27: getfield      #15                 // Field value:[C
  30: iload_2
  31: caload
  32: iadd
  33: istore_1
  34: iinc          2, 1
  37: iload_2
  38: aload_0
  39: getfield      #15                 // Field value:[C
  42: arraylength
  43: if_icmplt     22
  46: aload_0
  47: iload_1
  48: putfield      #17                 // Field hash:I
  51: iload_1
  52: ireturn

如果他们已将循环条件更改为也使用本地字段,

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

然后字节码实际变得更短,并且在循环中丢失可以说较慢的getfield调用,

 public int h1();
Code:
   0: aload_0       
   1: getfield      #17                 // Field hash:I
   4: istore_1      
   5: iload_1       
   6: ifne          50
   9: aload_0       
  10: getfield      #15                 // Field value:[C
  13: arraylength   
  14: ifle          50
  17: aload_0       
  18: getfield      #15                 // Field value:[C
  21: astore_2      
  22: iconst_0      
  23: istore_3      
  24: goto          39
  27: bipush        31
  29: iload_1       
  30: imul          
  31: aload_2       
  32: iload_3       
  33: caload        
  34: iadd          
  35: istore_1      
  36: iinc          3, 1
  39: iload_3       
  40: aload_2       
  41: arraylength   
  42: if_icmplt     27
  45: aload_0       
  46: iload_1       
  47: putfield      #17                 // Field hash:I
  50: iload_1       
  51: ireturn       

在我的jdk 1.7.0_79上进行几百万次循环遍历该方法的愚蠢测试始终如一地显示原始方法非常需要运行时间长5倍然后是未优化的“或正确”优化“的方法。后者2在性能上没有差异。

所以我想总的来说,在本地存储字段是尝试优化方法字节码,可能在jit能够优化它本身之前,但它们搞砸了它实际上使方法更糟糕...... < / p>

此代码实际上已在Java 9中修复, https://bugs.openjdk.java.net/browse/JDK-8058643