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

Mar*_*cio 6 java

这是什么的OpenJDK线的优点数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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,虽然参照private final char value[]被复制到本地val的循环内访问,其.length领域还在通过访问value,不是val.

我怀疑" 性能 "是答案(例如,从本地读取比从现场读取更快)但我会欣赏一个精确且易于阅读的答案,甚至可能有一些关于优势的数据.

And*_*rew 3

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

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;
}
Run Code Online (Sandbox Code Playgroud)

我们可以看到这两种方法生成几乎相同的字节码,但是我们的“优化”实现实际上最终使用了另外 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
Run Code Online (Sandbox Code Playgroud)

如果他们更改了循环条件以也使用本地字段,

...
for (int i = 0; i < val.length; i++) {
...
Run Code Online (Sandbox Code Playgroud)

然后字节码实际上变得更短并且丢失了循环中可能更慢的 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       
Run Code Online (Sandbox Code Playgroud)

在我的 jdk 1.7.0_79 上对方法进行几百万次循环的愚蠢测试一致表明,原始方法的运行时间比未“优化”或正确“优化”的方法要长大约 5 倍。后两者在性能上没有差异。

所以我想总而言之,在本地存储该字段是一种优化方法字节码的尝试,可能是在 jit 能够优化它本身之前,但他们把它搞砸了,实际上使该方法变得更糟......

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