这条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;作为答案(例如,从本地阅读比从现场阅读更快)但我会欣赏一个精确且易于阅读的答案,甚至可能有一些关于优势的数据。
答案 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