StringBuilderの一連の呼び出しappend(x).append(y)は、通常のsb.append(x)よりも高速です。 sb.append(y)

みなさん、こんにちは。StringBuffer legacyの最後の記事へのコメントに興味深いリンクがありました。 この記事には興味深いベンチマークがありますが、より多くのドラマを提供するために変更しました。



@BenchmarkMode(Mode.Throughput) @Fork(1) @State(Scope.Thread) @Warmup(iterations = 10, time = 1, batchSize = 1000) @Measurement(iterations = 40, time = 1, batchSize = 1000) public class Chaining { private String a1 = "111111111111111111111111"; private String a2 = "222222222222222222222222"; private String a3 = "333333333333333333333333"; @Benchmark public String typicalChaining() { return new StringBuilder().append(a1).append(a2).append(a3).toString(); } @Benchmark public String noChaining() { StringBuilder sb = new StringBuilder(); sb.append(a1); sb.append(a2); sb.append(a3); return sb.toString(); } }
      
      





結果:



 Benchmark Mode Cnt Score Error Units Chaining.noChaining thrpt 40 8408.703 ± 214.582 ops/s Chaining.typicalChaining thrpt 40 35830.907 ± 1277.455 ops/s
      
      





合計で、コールチェーンsb.append().append()



連結しますsb.append().append()



は4倍高速です...上記の記事の著者は、コールチェーンの場合、生成されるバイトコードが少ないため、実行速度が速いという事実によると主張しています。



それでは、確認しましょう。



バイトコードの違い?



仮説は、バイトコードに入らず簡単に確認できます。典型的なUriBuilderを作成しましょう。



 public class UriBuilder { private String schema; private String host; private String path; public UriBuilder setSchema(String schema) { this.schema = schema; return this; } ... @Override public String toString() { return schema + "://" + host + path; } }
      
      





ベンチマークを繰り返します。



 @BenchmarkMode(Mode.Throughput) @Fork(1) @State(Scope.Thread) @Warmup(iterations = 10, time = 1, batchSize = 1000) @Measurement(iterations = 40, time = 1, batchSize = 1000) public class UriBuilderChaining { private String host = "host"; private String schema = "http"; private String path = "/123/123/123"; @Benchmark public String chaining() { return new UriBuilder().setSchema(schema).setHost(host).setPath(path).toString(); } @Benchmark public String noChaining() { UriBuilder uriBuilder = new UriBuilder(); uriBuilder.setSchema(schema); uriBuilder.setHost(host); uriBuilder.setPath(path); return uriBuilder.toString(); } }
      
      





理由が実際にバイトコードの量である場合、文字列連結などの重い操作でも、違いがわかるはずです。



結果:



 Benchmark Mode Cnt Score Error Units UriBuilderChaining.chaining thrpt 40 35797.519 ± 2051.165 ops/s UriBuilderChaining.noChaining thrpt 40 36080.534 ± 1962.470 ops/s
      
      





うーん...違いはエラーのレベルです。 したがって、バイトコードの数はそれとは何の関係もありません。 異常はStringBuilder



append()



で現れるので、これはおそらくよく知られているJVMオプション+XX:OptimizeStringConcat



と接続されています。 見てみましょう。 最初のテストを繰り返しますが、オプションは無効になっています。



JMHでは、アノテーションを使用して、次のようにこれを行うことができます。



 @Fork(value = 1, jvmArgsAppend = "-XX:-OptimizeStringConcat")
      
      





最初のテストを繰り返します。



 Benchmark Mode Cnt Score Error Units Chaining.noChaining thrpt 40 7598.743 ± 554.192 ops/s Chaining.typicalChaining thrpt 40 7946.422 ± 313.967 ops/s
      
      





ビンゴ!



x + y



を介して文字列を結合することx + y



あらゆるアプリケーションで非常に一般的な操作であるため、Hotspot JVMはnew StringBuilder().append(x).append(y).toString()



検出し、バイトコードのnew StringBuilder().append(x).append(y).toString()



パターンを作成せずに最適化されたマシンコードに置き換えます中間オブジェクト。



残念ながら、この最適化はsb.append(x); sb.append(y);



は適用されませんsb.append(x); sb.append(y);



sb.append(x); sb.append(y);



。 大きな線の違いは、桁違いになります。



結論



可能な場合は、メソッドチェーンパターンを使用します。 まず、 StringBuilder



の場合StringBuilder



これはJITが文字列の連結を最適化するのに役立ちます。 次に、この方法で生成されるコードのバイト数が少なくなり、場合によってはメソッドをインライン化するのに役立ちます。



SOに関する質問

Shipilevからの汚い詳細を含む関連レポート



All Articles