3

jdk ソース コード、より具体的にはコレクション フレームワークでは、式で変数を読み取る直前に変数を割り当てることが優先されることに気付きました。それは単純な好みですか、それとも私が気づいていないもっと重要なことですか? 考えられる理由の 1 つは、変数がこの式でのみ使用されていることです。

私はこのスタイルに慣れていないので、読むのが難しいと思います。コードは非常に凝縮されています。以下に、から取られた例を見ることができます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 && ...) {
   ...
}
4

1 に答える 1

7

コメントで既に述べたように、コレクション フレームワークと同時実行パッケージの主な作成者の 1 人である Doug Lea は、単なる人間にとっては紛らわしい (または直感に反する) ように見える最適化を行う傾向があります。

ここでの「有名な」例は、バイトコードのサイズを最小限に抑えるためにフィールドをローカル変数にコピーすることですtable。これは、実際には、参照した例のフィールドとローカルtab変数でも行われます!


非常に単純なテストでは、アクセスが「インライン化」されているかどうかに関係なく (結果のバイトコード サイズを参照して) 違いはないようです。だから私はあなたが言及したメソッドの構造に大まかに似た例を作成しようとしましたgetNode:配列であるフィールドへのアクセス、長さチェック、1つの配列要素のフィールドへのアクセス...

  • メソッドは個別のtestSeparate割り当てとチェックを行います
  • このtestInlinedメソッドは、代入式 if スタイルを使用します
  • メソッドは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 が作動し、結果のマシン コードは元のバイトコードとは「何も」関係ありません。3 つのバージョンすべてが実際には最終的に同じマシンコードにコンパイルされます。

つまり、結論は次のとおりです。このスタイルは使用しないでください。代わりに、読みやすく保守しやすいダム コードを記述してください。このような「最適化」を使用する番がいつになるかがわかります。


編集:短い補遺...

さらにテストを行い、JIT によって生成される実際のマシン コードtestSeparateについて、 andメソッドを比較しました。testInlined

非現実的な過度の最適化や JIT が取る可能性のあるその他の近道を避けるために、メソッドを少し変更しましたmainが、実際のメソッドは変更されていません。

予想どおり、ホットスポット逆アセンブリ JVM と でメソッドを数千回呼び出すと、-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:+PrintAssembly両方のメソッドの実際のマシン コードは同じになります。

繰り返しますが、JIT はその仕事をうまくこなし、プログラマーは(それが何を意味するにせよ)読みやすいコードを書くことに集中できます。

...そして、マイナーな修正/明確化:

3 番目の方法はテストしませんでした。これは、他の方法と同等testRepeatedではないためです(したがって、同じマシン コードを生成することはできません)。ところで、これは、フィールドをローカル変数に格納する戦略のもう 1 つの小さな利点です。これは、(非常に制限されていますが、時には便利な) 形式の "スレッド セーフ" を提供します: 配列の長さ (のメソッド内の配列は、メソッドの実行中に変更できません。tabgetNodeHashMap

于 2015-03-10T23:40:39.603 に答える