簡體   English   中英

為什么 jdk 代碼風格使用變量賦值並在同一行讀取 - 例如。 (i=2) < 最大值

[英]Why jdk code style uses a variable assignment and read on the same line - eg. (i=2) < max

我注意到在 jdk 源代碼中,更具體地說,在 collections 框架中,在表達式中讀取變量之前,優先分配變量。 這只是一個簡單的偏好還是我不知道的更重要的東西? 我能想到的一個原因是該變量僅在此表達式中使用。

由於我不習慣這種風格,我覺得很難閱讀。 代碼非常簡潔。 下面您可以看到取自java.util.HashMap.getNode()的示例

Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 && ...) {
   ...
}

正如評論中已經提到的那樣:Doug Lea是集合框架和並發軟件包的主要作者之一,他們傾向於進行優化,這些優化對於凡人而言可能看起來令人困惑(甚至違反直覺)。

這里的一個“着名”示例是將字段復制到局部變量 ,以便最小化字節碼的大小,實際上也是在您引用的示例中使用table字段和本地tab變量完成的!


對於非常簡單的測試,它似乎沒有區別(指的是結果字節碼大小)訪問是否“內聯”。 所以我嘗試創建一個大致類似於你提到的getNode方法結構的例子:訪問一個數組的字段,一個長度檢查,一個數組元素字段的訪問......

  • testSeparate方法確實分配了分配和檢查
  • testInlined方法使用testInlined -in-if-style
  • 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將啟動,並且生成的機器代碼將與原始字節碼“無關” - 我很確定所有三個版本實際上都是最后編譯成相同的機器代碼。

所以底線是:不要使用這種風格。 相反,只需編寫易於閱讀和維護的啞代碼即可。 你知道什么時候輪到你使用這樣的“優化”了。


編輯:一個簡短的附錄......

我進行了進一步的測試,並比較了關於JIT生成的實際機器代碼testSeparatetestInlined方法。

我稍微修改了main方法,以防止JIT可能采取的不切實際的過度優化或其他快捷方式,但實際方法未經修改。

正如所料:當使用熱點反匯編JVM和-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:+PrintAssembly調用方法幾千次時,兩種方法的實際機器代碼是相同的

因此,JIT再次完成其工作,並且程序員可以專注於編寫可讀代碼(無論這意味着什么)。

......以及一個小的糾正/澄清:

我沒有測試的第三種方法中, testRepeated ,因為它是不等同於其他方法(因此,它不能導致相同的機器代碼)。 順便說一句,這是在局部變量中存儲字段的策略的另一個小優點:它提供了一種( 非常有限但有時很方便)的“ 線程安全 ”形式:它確保了數組的長度(如正在執行方法時, HashMapgetNode方法中的tab數組不能更改。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM