2016-10-26 3 views
7

私は、より最近のバージョンのJavaで文字列の連結Javaは+との文字列連結をどれくらい最適化しますか?

String test = one + "two"+ three; 

StringBuilderを使用するように最適化されますことを知っています。

しかし、この行に当たるたびに新しいStringBuilderが生成されるか、すべての文字列連結に使用される単一のスレッドローカルStringBuilderが生成されますか?

つまり、頻繁に呼び出されるメソッドのパフォーマンスを改善するには、独自のスレッドローカルStringBuilderを再作成するか、そうすることで大きな利益は得られませんか?

私はちょうどこれのためのテストを書くことができますが、それはコンパイラ/ JVM固有のものか、より一般的に答えることができるものかどうか疑問に思いますか?

+1

式を連結するときの再入可能性に注意してください。 – SLaks

+1

最後に、それはかなり愚かで、 'StringBuilder'に繰り返し割り当てを強制しました。しかし、これはOracleのJDKに固有のもので、結果として生じるバイトコードを見ているため、JVMが行う可能性のある最適化を考慮していませんでした。私のルールは、あなたが気にしない時間の99.999%です。あなたが気にしている.001%のために、合計結果を処理するのに十分な大きさに割り当てられた明示的な 'StringBuilder'を使用してください。 –

+0

1行だけではなく、文字列操作を多くしない限り、私はT.J.に同意します。何も違いは見られません。 JVMは、実際には、スレッドのローカルにすべてのメモリを割り当てます(別のスレッドと共有する必要があるまで)。つまり、スレッドのローカルにはおそらく何の効果もありません。 – markspace

答えて

6

私が知る限り、StringBuilderインスタンスを再利用するコンパイラを生成するコンパイラはありません。特に、javacとECJは再利用コードを生成しません。

このような再利用をしないことが妥当であることを強調することが重要です。 ThreadLocal変数からインスタンスを取得するコードが、TLABのプレーンな割り当てよりも高速であると仮定することは安全ではありません。そのインスタンスを再利用するためのローカルGCサイクルの潜在的なコストを追加しようとしても、コストの分数を特定できる限り、そのことを結論づけることはできません。

したがって、ビルダーを再利用しようとするコードは、パフォーマンスが明らかに向上することなく、実際に再利用されるかどうかを知らずにビルダーを生かし続けるため、メモリーを浪費します。

は、特に我々はさらにホットスポットのような

  • のJVM上の文にまとめてこれらのような純粋な地元の割り当てをElideのできる、エスケープ解析有し、また、配列のコピーコストをElideの操作のサイズを変更することを検討する際に
  • このような洗練されたJVMは通常、具体的にはStringBuilderベースの連結に専用化された最適化をも有しており、コンパイルされたコードが共通パターンに従う場合に最もよく機能する。

Java 9では、画像が再び変更されます。その後、文字列連結はinvokedynamic命令にコンパイルされ、実行時にJRE提供ファクトリにリンクされます(StringConcatFactory参照)。次に、JREはコードがどのように見えるかを決定します。特定のJVMにメリットがある場合は、バッファの再使用を含めて、特定のJVMに合わせることができます。これは、割り当てのシーケンスではなく単一の命令と、StringBuilderへの複数の呼び出しを必要とするため、コードサイズも削減します。

+1

とjdk-9の画像が変わる*劇的に* again :) – Eugene

6

jdk-9文字列連結にどれくらいの労力を費やしたかは驚くでしょう。最初のjavacはStringBuilder#appendへの呼び出しの代わりにinvokedynamicを発行します。 invokedynamicはCallSiteを返し、MethodHandle(実際は一連のMethodHandlesです)を含みます。

したがって、文字列連結に対して実際に何が行われたかは、ランタイムに移されます。欠点は、同じタイプの引数に対して、より遅くなるStringsを初めて連結することです。

は、その後、あなたが文字列を連結するときから選択することができます戦略のシリーズは、(あなたがjava.lang.invoke.stringConcatパラメータでデフォルトのものをオーバーライドすることができます)があります。

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 
} 

デフォルトの戦略は次のとおりです。MH_INLINE_SIZED_EXACT獣です!

は、それは文字列(最速)を構築するために、パッケージ・プライベートコンストラクタを使用しています。

/* 
* Package private constructor which shares value array for speed. 
*/ 
String(byte[] value, byte coder) { 
    this.value = value; 
    this.coder = coder; 
} 

まずこの戦略はそうフィルタはと呼ばれる作成されます。これらは基本的に、着信パラメータをString値に変換するメソッドハンドルです。

String.valueOf(YourInstance) 

だから、あなたが委任します3 MethodHandlesがあるだろう連結する3つのオブジェクトを持っている場合:1が予想されるように、これらのMethodHandlesは、ほとんどの場合、呼び出しをMethodHandleを生成することStringifiersと呼ばれるクラスに格納されていますString.valueOf(YourObject)にすると、オブジェクトを文字列に変換したことを意味します。 このクラスの中にはまだ理解できないものがあります。別のクラスStringifierMost(これはStringのみのリファレンス、floatとdoubleに変換されます)とStringifierAnyを持つ必要があります。

MH_INLINE_SIZED_EXACTでは、バイト配列は正確なサイズに計算されるため、それを計算する方法があります。

これは、入力パラメータ(References/float/double)の文字列化されたバージョンを取るStringConcatHelper#mixLenのメソッドによって行われます。この時点で、私たちは最終的なStringのサイズを知っています。さて、我々は実際にそれを知らない、我々はそれを計算するMethodHandleがあります。

ここで言及する価値のある文字列jdk-9にもう1つ変更があります。coderフィールドが追加されました。これは、Stringのsize/equality/charAtを計算するために必要です。サイズに必要なので、それも計算する必要があります。これはStringConcatHelper#mixCoderによって行われます。

それはウル配列を作成しますMethodHandleを委任するために、この時点では安全である:

@ForceInline 
    private static byte[] newArray(int length, byte coder) { 
     return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder); 
    } 

は、どのように各要素が追加されますか?方法はStringConcatHelper#prependです。

今や、バイトを取るStringのコンストラクタを呼び出すために必要なすべての詳細が必要になります。


すべてのこれらの操作(および他の多くの私は簡単にするためにスキップされている)は、添付が実際に発生したときに呼び出されますMethodHandle発光を介して処理されています。

+1

このような簡単な操作の詳細がIMOを魅了しているという単純な理由から、 – Eugene

+0

残念ながら、それは本当に直接質問に答えるわけではありませんが、本当に面白いです - 私はチックを移動することができないように感じています:( –

+1

@TimBは完全に同意します。受け入れられた答えは正しいものです。 – Eugene

関連する問題