@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からの汚い詳細を含む関連レポート 。