为什么jdk代码样式使用变量分配并在同一行上读取-例如 (i = 2)<最大值

为什么jdk代码样式使用变量分配并在同一行上读取-例如 (i = 2)<最大值,第1张

为什么jdk代码样式使用变量分配并在同一行上读取-例如 (i = 2)<最大值

正如评论中已经提到的那样:集合框架和并发包的主要作者之一道格·李(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



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5475505.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-12

发表评论

登录后才能评论

评论列表(0条)

保存