文字列連結はJava 9でどのように実装されていますか?


111

JEP 280で記述されているように、文字列連結を示します。

Stringによって生成された静的連結バイトコードシーケンスを変更して、JDKライブラリ関数の呼び出しをjavac使用しinvokedynamicます。これStringにより、によってエミットされたバイトコードをさらに変更することなく、将来の連結の最適化が可能になりますjavac

ここで、invokedynamic呼び出しの使用方法と、バイトコードの連結がどのように異なるのかを理解したいと思いinvokedynamicます。


11
しばらく前にそのことについて書いた -それが助けになるなら、私はそれを答えに凝縮するでしょう。
ニコライ

10
:また、きれいに新しい文字列連結機構のポイントを説明し、このビデオを見てyoutu.be/wIyeOaitmWM?t=37m58s
ZhekaKozlov

3
@ZhekaKozlov私はあなたのコメントを2度投票できればいいのにと思っています。
ユージーン

2
@Nicolai:それは素晴らしいだろうし、ここ(私のものを含む)の他のどれよりも良い答えになるだろう。私の回答のどの部分を組み込むかは自由です。広い回答の一部として(基本的に)すべてを含めた場合、私は削除します。あるいは、それが非常に目に見えるのであなたが私の答えに単に追加したいのであれば、私はそれをコミュニティウィキにしました。
TJクラウダー、2017年

回答:


95

「古い」方法は、一連のStringBuilder指向の操作を出力します。このプログラムを考えてみましょう:

public class Example {
    public static void main(String[] args)
    {
        String result = args[0] + "-" + args[1] + "-" + args[2];
        System.out.println(result);
    }
}

これをJDK 8以前でコンパイルしてjavap -c Exampleからバイトコードを確認すると、次のようになります。

パブリッククラスの例{
  public Example();
    コード:
       0:aload_0
       1:invokespecial#1 //メソッドjava / lang / Object。 "<init>" :()V
       4:戻る

  public static void main(java.lang.String []);
    コード:
       0:新しい#2 //クラスjava / lang / StringBuilder
       3:重複
       4:invokespecial#3 //メソッドjava / lang / StringBuilder。 "<init>" :()V
       7:aload_0
       8:iconst_0
       9:aaload
      10:invokevirtual#4 //メソッドjava / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder;
      13:ldc#5 //文字列-
      15:invokevirtual#4 //メソッドjava / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder;
      18:aload_0
      19:iconst_1
      20:aaload
      21:invokevirtual#4 //メソッドjava / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder;
      24:ldc#5 //文字列-
      26:invokevirtual#4 //メソッドjava / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder;
      29:aload_0
      30:iconst_2
      31:aaload
      32:invokevirtual#4 //メソッドjava / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder;
      35:invokevirtual#6 //メソッドjava / lang / StringBuilder.toString :()Ljava / lang / String;
      38:astore_1
      39:getstatic#7 //フィールドjava / lang / System.out:Ljava / io / PrintStream;
      42:aload_1
      43:invokevirtual#8 //メソッドjava / io / PrintStream.println:(Ljava / lang / String;)V
      46:戻る
}

ご覧のとおり、を作成してStringBuilderを使用していappendます。組み込みバッファーのデフォルトの容量はStringBuilder16文字しかないため、これはかなり非効率的であることが有名です。コンパイラーが事前にさらに割り当てることを知る方法がないため、最終的には再割り当てする必要があります。また、メソッド呼び出しの集まりでもあります。(ただし、JVMはこれらの呼び出しパターンを検出および書き換えて、より効率的にすることができることに注意してください。)

Java 9が生成するものを見てみましょう。

パブリッククラスの例{
  public Example();
    コード:
       0:aload_0
       1:invokespecial#1 //メソッドjava / lang / Object。 "<init>" :()V
       4:戻る

  public static void main(java.lang.String []);
    コード:
       0:aload_0
       1:iconst_0
       2:aaload
       3:aload_0
       4:iconst_1
       5:aaload
       6:aload_0
       7:iconst_2
       8:aaload
       9:invokedynamic#2、0 // InvokeDynamic#0:makeConcatWithConstants:(Ljava / lang / String; Ljava / lang / String; Ljava / lang / String;)Ljava / lang / String;
      14:astore_1
      15:getstatic#3 //フィールドjava / lang / System.out:Ljava / io / PrintStream;
      18:aload_1
      19:invokevirtual#4 //メソッドjava / io / PrintStream.println:(Ljava / lang / String;)V
      22:戻る
}

ああ、でもそれはもっと短いです。:-)これは、Javadocでこれを示すmakeConcatWithConstantsfrom への単一の呼び出しを行いますStringConcatFactory

既知の型の既知の数の引数を効率的に連結するために使用できる文字列連結メソッドの作成を容易にするメソッド。これらのメソッドは通常、Javaプログラミング言語の文字列連結機能をサポートするために、invokedynamic呼び出しサイトのブートストラップメソッドとして使用されます。


41
これは、私がほぼ6年前にその日に書いた回答を思い出させます:stackoverflow.com/a/7586780/330057-誰かが、StringBuilderを作成するか+=、forループでプレーンオールドを使用するかを尋ねました。私はそれが依存していると彼らに言いました、しかし彼らがいつか将来連結をひもでつなぐより良い方法を見つけるかもしれないことを忘れないでください。キーラインは本当に最後から二番目の行である:So by being smart, you have caused a performance hit when Java got smarter than you.
corsiKa

3
@corsiKa:LOL!しかし、すごい、そこにたどり着くまでに長い時間がかかりました(6年という意味ではなく、22年ほどということです... :
TJクラウダー

1
@supercat:私が理解しているように、いくつかの理由があります。特に、パフォーマンスクリティカルパス上のメソッドに渡すvarargs配列を作成することが理想的ではないということです。また、を使用invokedynamicすると、メソッド呼び出しのオーバーヘッドや各呼び出しでのテーブルのディスパッチを行わずに、実行時にさまざまな連結戦略を選択して最初の呼び出しにバインドできます。詳細はニコライの記事とJEPをご覧ください
TJクラウダー、2017年

1
@supercat:そして、最終的な結果に変換されるのではなく、文字列に事前に変換する必要があるため、文字列以外ではうまく機能しないという事実があります。より非効率。それを作ることObjectができましたが、その後、すべてのプリミティブをボックス化する必要があります...(ニコライが優れた記事でカバーしているのは、その前です)
TJクラウダー

2
@supercat私はString.concat(String)、実装が結果の文字列の配列をインプレースで作成する既存のメソッドを参照していました。toString()任意のオブジェクトを呼び出さなければならない場合、利点は意味がなくなります。同様に、配列を受け入れるメソッドを呼び出す場合、呼び出し元は配列を作成して埋める必要があるため、全体的なメリットが減少します。しかし、今は関係ありません。新しいソリューションは基本的にあなたが考えていたものなので、ボクシングのオーバーヘッドがなく、配列を作成する必要がなく、バックエンドが特定のシナリオ用に最適化されたハンドラーを生成する場合があるためです。
Holger

20

invokedynamic文字列連結の最適化に使用される実装の詳細に入る前に、私の意見では、invokedynamicの背景と使用方法を理解する必要があります。

この invokedynamic 命令は、JVM上の動的言語用のコンパイラとランタイムシステムの実装を簡素化し、潜在的に改善します。これを行うには、言語の実装者がinvokedynamic、以下の手順を含む命令を使用してカスタムリンケージ動作を定義できるようにします。


文字列連結の最適化を実装するためにもたらされた変更を使用して、これらを試してみます。

  • ブートストラップ法の定義: - Java9、ブートストラップ法でinvokedynamic呼び出しサイトは、主に文字列連結をサポートするmakeConcatmakeConcatWithConstantsして導入されたStringConcatFactory実装。

    invokedynamicの使用は、実行時まで翻訳戦略を選択する代替手段を提供します。で使用される変換戦略は、StringConcatFactoryに似ているLambdaMetafactory、以前のJavaバージョンで導入されたとして。さらに、質問で言及されたJEPの目標の1つは、これらの戦略をさらに拡大することです。

  • 定数プールエントリの指定:-これらは、invokedynamic命令のMethodHandles.Lookupコンテキストでメソッドハンドルを作成するためのファクトリである(1)オブジェクト以外の命令への追加の静的引数ですinvokedynamic。(2)Stringオブジェクト、動的呼び出しで言及されたメソッド名サイトと(3)MethodTypeオブジェクト、動的呼び出しサイトの解決された型シグネチャ。

    コードのリンク時にすでにリンクされています。実行時に、ブートストラップメソッドが実行され、連結を行う実際のコードにリンクされます。invokedynamic呼び出しを適切なinvokestatic呼び出しに書き換えます。これにより、定数プールから定数文字列がロードされ、ブートストラップメソッドの静的引数が活用されて、これらおよびその他の定数がブートストラップメソッド呼び出しに直接渡されます。

  • invokedynamic命令の使用:-これは、最初の呼び出し中に呼び出しターゲットを1回ブートストラップする手段を提供することにより、遅延リンケージのための機能を提供します。ここでの最適化の具体的なアイデアは、StringBuilder.appendダンス全体をへの単純なinvokedynamic呼び出しに置き換えることjava.lang.invoke.StringConcatFactoryです。これにより、連結が必要な値を受け入れます。

Indify文字列の連結の例に提案状態で共有と同様の方法Java9とアプリケーションのベンチマーク@TJクラウダーがコンパイルされたバイトコードの違いは、様々なインプリメンテーションの間でかなり見えます。


17

ここで少し詳細を追加します。取得する主要な部分は、文字列の連結がどのように行われるかが実行時の決定であり、コンパイル時の決定ではなくなったことです。したがって、変更される可能性があります。つまり、Java-9に対してコードを1回コンパイルし、再コンパイルする必要なく、基盤となる実装を変更できます。

2つ目のポイントは、現時点では6 possible strategies for concatenation of String次のようになっていることです。

 private enum Strategy {
    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder}.
     */
    BC_SB,

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but trying to estimate the required storage.
     */
    BC_SB_SIZED,

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but computing the required storage exactly.
     */
    BC_SB_SIZED_EXACT,

    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also tries to estimate the required storage.
     */
    MH_SB_SIZED,

    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also estimate the required storage exactly.
     */
    MH_SB_SIZED_EXACT,

    /**
     * MethodHandle-based generator, that constructs its own byte[] array from
     * the arguments. It computes the required storage exactly.
     */
    MH_INLINE_SIZED_EXACT
}

パラメータを介してそれらのいずれかを選択できます-Djava.lang.invoke.stringConcatStringBuilderまだオプションであることに注意してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.