正如评论中已经提到的那样:集合框架和并发包的主要作者之一道格·李(Doug Lea)倾向于进行优化,这些优化对于凡人来说似乎令人困惑(甚至违反直觉)。
这里的“著名”示例是将字段复制到局部变量,以最大程度地减少字节码的大小,这实际上也是在您所引用的示例中使用
table字段和局部
tab变量来完成的!
对于非常简单的测试,访问是否“内联”似乎没有什么不同(指所得到的字节码大小)。因此,我尝试创建一个与
getNode您提到的方法的结构大致相似的示例:对作为数组的字段的访问,长度检查,对一个数组元素的字段的访问…
- 该
testSeparate
方法单独进行分配和检查 - 该
testInlined
方法使用样式分配 - 该
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
该
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
因此,看来这种“晦涩”的查询和赋值方式确实可以节省几个字节的字节码,并且(考虑到链接的答案中关于将字段存储在局部变量中的理由),这可能是使用它的原因。这种风格。
但…无论如何:在执行该方法几次之后,JIT将启动,并且生成的机器代码将与原始字节码“无关”-我很确定这三个版本实际上都是最后编译为相同的机器代码。
因此,最重要的是:不要使用这种样式。相反,只需编写易于阅读和维护的愚蠢代码。您将知道该轮到使用此类“优化”的时候了。
编辑:简短的附录…
我进行了进一步的测试,并比较了
testSeparate与JIT生成
testInlined的实际 机器代码 有关的和方法。
我对
main方法进行了一些修改,以防止JIT可能采取的不切实际的过度优化或其他捷径,但实际方法并未进行任何修改。
和预期的一样:当使用热点反汇编JVM和调用该方法数千次时
-XX:+UnlockDiagnosticVMOptions-XX:+LogCompilation -XX:+PrintAssembly,这两个方法的实际机器代码是 相同的 。
因此,JIT再次表现出色,程序员可以专注于编写 可读 代码(无论如何)。
…以及一些小的更正/说明:
我没有测试第三方法中,
testRepeated因为它是 不等同 于其他方法(因此,它 可以
不产生相同的机器代码)。顺便说一下,这是将字段存储在局部变量中的策略的另一个小优点:它提供了( 非常 有限,但有时很方便)“ 线程安全 ”形式:确保数组的长度(例如执行方法时,)中的
tab数组无法更改。
getNode``HashMap
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)