正如评论中已经提到的:Doug Lea 是集合框架和并发包的主要作者之一,他倾向于进行一些对于普通人来说可能看起来令人困惑(甚至违反直觉)的优化。
A "famous" example here is copying fields to local variables https://stackoverflow.com/questions/2785964/in-arrayblockingqueue-why-copy-final-member-field-into-local-final-variable in order to minimize the size of the bytecode, which is actually also done with the table
field and the local tab
variable in the example that you referred to!
对于非常简单的测试,无论访问是否“内联”,似乎都没有什么区别(指的是生成的字节码大小)。所以我尝试创建一个大致类似于结构的示例getNode
您提到的方法:访问数组字段、长度检查、访问一个数组元素的字段......
- The
testSeparate
方法将赋值和检查分开
- The
testInlined
方法使用 if 风格的赋值
- The
testRepeated
方法(作为反例)重复执行每个访问
代码:
class Node
{
int k;
int j;
}
public class AssignAndUseTestComplex
{
public static void main(String[] args)
{
AssignAndUseTestComplex t = new AssignAndUseTestComplex();
t.testSeparate(1);
t.testInlined(1);
t.testRepeated(1);
}
private Node table[] = new Node[] { new Node() };
int testSeparate(int value)
{
Node[] tab = table;
if (tab != null)
{
int n = tab.length;
if (n > 0)
{
Node first = tab[(n-1)];
if (first != null)
{
return first.k+first.j;
}
}
}
return 0;
}
int testInlined(int value)
{
Node[] tab; Node first, e; int n;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1)]) != null) {
return first.k+first.j;
}
return 0;
}
int testRepeated(int value)
{
if (table != null)
{
if (table.length > 0)
{
if (table[(table.length-1)] != null)
{
return table[(table.length-1)].k+table[(table.length-1)].j;
}
}
}
return 0;
}
}
以及生成的字节码:testSeparate
方法用途41 条指令:
int testSeparate(int);
Code:
0: aload_0
1: getfield #15 // Field table:[Lstackoverflow/Node;
4: astore_2
5: aload_2
6: ifnull 40
9: aload_2
10: arraylength
11: istore_3
12: iload_3
13: ifle 40
16: aload_2
17: iload_3
18: iconst_1
19: isub
20: aaload
21: astore 4
23: aload 4
25: ifnull 40
28: aload 4
30: getfield #37 // Field stackoverflow/Node.k:I
33: aload 4
35: getfield #41 // Field stackoverflow/Node.j:I
38: iadd
39: ireturn
40: iconst_0
41: ireturn
The testInlined
方法确实有点小,39 条指令
int testInlined(int);
Code:
0: aload_0
1: getfield #15 // Field table:[Lstackoverflow/Node;
4: dup
5: astore_2
6: ifnull 38
9: aload_2
10: arraylength
11: dup
12: istore 5
14: ifle 38
17: aload_2
18: iload 5
20: iconst_1
21: isub
22: aaload
23: dup
24: astore_3
25: ifnull 38
28: aload_3
29: getfield #37 // Field stackoverflow/Node.k:I
32: aload_3
33: getfield #41 // Field stackoverflow/Node.j:I
36: iadd
37: ireturn
38: iconst_0
39: ireturn
最后,testRepeated
方法使用了高达63条指令
int testRepeated(int);
Code:
0: aload_0
1: getfield #15 // Field table:[Lstackoverflow/Node;
4: ifnull 62
7: aload_0
8: getfield #15 // Field table:[Lstackoverflow/Node;
11: arraylength
12: ifle 62
15: aload_0
16: getfield #15 // Field table:[Lstackoverflow/Node;
19: aload_0
20: getfield #15 // Field table:[Lstackoverflow/Node;
23: arraylength
24: iconst_1
25: isub
26: aaload
27: ifnull 62
30: aload_0
31: getfield #15 // Field table:[Lstackoverflow/Node;
34: aload_0
35: getfield #15 // Field table:[Lstackoverflow/Node;
38: arraylength
39: iconst_1
40: isub
41: aaload
42: getfield #37 // Field stackoverflow/Node.k:I
45: aload_0
46: getfield #15 // Field table:[Lstackoverflow/Node;
49: aload_0
50: getfield #15 // Field table:[Lstackoverflow/Node;
53: arraylength
54: iconst_1
55: isub
56: aaload
57: getfield #41 // Field stackoverflow/Node.j:I
60: iadd
61: ireturn
62: iconst_0
63: ireturn
因此,这种编写查询和赋值的“模糊”方式确实可以节省一些字节码,并且(考虑到有关在局部变量中存储字段的链接答案中的理由)这可能是使用的原因这种风格。
But...
无论如何:该方法执行几次后,JIT 将启动,生成的机器代码将与原始字节码“无关” - 而且我很确定所有三个版本实际上都是最终编译成相同的机器码。
所以底线是:不要使用这种风格。相反,只需写愚蠢的代码 http://www.oracle.com/technetwork/articles/java/devinsight-1-139780.html易于阅读和维护。您会知道什么时候轮到您使用此类“优化”。
编辑:一个简短的附录...
我做了进一步的测试,并进行了比较testSeparate
and testInlined
方法与实际情况有关机器码由 JIT 生成。
我修改了main
方法有点,以防止不切实际的过度优化或 JIT 可能采取的其他捷径,但实际方法未作修改。
正如预期的那样:当使用热点反汇编 JVM 调用方法几千次时,-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:+PrintAssembly
,那么这两种方法的实际机器码都是完全相同的.
因此,JIT 再次很好地完成了它的工作,程序员可以专注于编写readable代码(无论这意味着什么)。
...以及一个小的更正/澄清:
第三种方法我没有测试,testRepeated
, 因为它是不等同到其他方法(因此,它can不会产生相同的机器代码)。顺便说一句,这是在局部变量中存储字段的策略的另一个小优点:它提供了 (very有限但有时很方便)形式的“线程安全": 确保数组的长度(如tab
数组中的getNode
的方法HashMap
) 在方法执行时不能更改。