整数プリミティブを使用して文字列に変換することに関する少しの議論を調査しているときに、次のJMHマイクロベンチマークを作成しました。"" + n
Integer.toString(int)
@Fork(1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class IntStr {
protected int counter;
@GenerateMicroBenchmark
public String integerToString() {
return Integer.toString(this.counter++);
}
@GenerateMicroBenchmark
public String stringBuilder0() {
return new StringBuilder().append(this.counter++).toString();
}
@GenerateMicroBenchmark
public String stringBuilder1() {
return new StringBuilder().append("").append(this.counter++).toString();
}
@GenerateMicroBenchmark
public String stringBuilder2() {
return new StringBuilder().append("").append(Integer.toString(this.counter++)).toString();
}
@GenerateMicroBenchmark
public String stringFormat() {
return String.format("%d", this.counter++);
}
@Setup(Level.Iteration)
public void prepareIteration() {
this.counter = 0;
}
}
Linux マシン (最新の Mageia 4 64 ビット、Intel i7-3770 CPU、32GB RAM) に存在する両方の Java VM で、デフォルトの JMH オプションを使用して実行しました。最初の JVM は、Oracle JDK 8u5 64 ビットで提供されたものでした。
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
この JVM を使用すると、期待どおりの結果が得られました。
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 32317.048 698.703 ops/ms
b.IntStr.stringBuilder0 thrpt 20 28129.499 421.520 ops/ms
b.IntStr.stringBuilder1 thrpt 20 28106.692 1117.958 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20066.939 1052.937 ops/ms
b.IntStr.stringFormat thrpt 20 2346.452 37.422 ops/ms
つまり、オブジェクトをStringBuilder
作成して空の文字列を追加する追加のオーバーヘッドにより、クラスの使用が遅くなります。StringBuilder
使用String.format(String, ...)
はさらに遅く、桁違いに遅くなります。
一方、ディストリビューションで提供されるコンパイラは、OpenJDK 1.7 に基づいています。
java version "1.7.0_55"
OpenJDK Runtime Environment (mageia-2.4.7.1.mga4-x86_64 u55-b13)
OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)
ここでの結果は興味深いものでした:
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 31249.306 881.125 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39486.857 663.766 ops/ms
b.IntStr.stringBuilder1 thrpt 20 41072.058 484.353 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20513.913 466.130 ops/ms
b.IntStr.stringFormat thrpt 20 2068.471 44.964 ops/ms
この JVM を使用すると、なぜStringBuilder.append(int)
これほど高速に見えるのでしょうか? クラスStringBuilder
のソース コードを調べても、特に興味深いことは何もありませんでした。問題のメソッドは とほとんど同じですInteger#toString(int)
。興味深いことに、Integer.toString(int)
(stringBuilder2
マイクロベンチマーク) の結果を追加しても、それほど速くはないようです。
このパフォーマンスの不一致は、テスト ハーネスの問題ですか? または、私の OpenJDK JVM には、この特定のコード (アンチ) パターンに影響を与える最適化が含まれていますか?
編集:
より単純な比較のために、Oracle JDK 1.7u55 をインストールしました。
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)
結果は、OpenJDK の結果と同様です。
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 32502.493 501.928 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39592.174 428.967 ops/ms
b.IntStr.stringBuilder1 thrpt 20 40978.633 544.236 ops/ms
これは、より一般的な Java 7 と Java 8 の問題のようです。おそらく、Java 7 ではより積極的な文字列の最適化が行われたのでしょうか?
編集2:
完全を期すために、これらの両方の JVM の文字列関連の VM オプションを次に示します。
Oracle JDK 8u5 の場合:
$ /usr/java/default/bin/java -XX:+PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
OpenJDK 1.7 の場合:
$ java -XX:+PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
bool UseStringCache = false {product}
このUseStringCache
オプションは Java 8 で削除され、代替品はありませんでした。残りのオプションは同じ設定になっているようです。
編集3:
のソース コードとのファイルAbstractStringBuilder
のクラスを並べて比較すると、注目に値するものは何もありません。多くの表面的な変更とドキュメントの変更は別として、は符号なし整数をサポートするようになり、 とより多くのコードを共有するためにわずかにリファクタリングされました。これらの変更は、 で使用されるコード パスに影響を与えるものではないようですが、見落としがあるかもしれません。StringBuilder
Integer
src.zip
Integer
StringBuilder
StringBuffer
StringBuilder#append(int)
IntStr#integerToString()
と のために生成されたアセンブリ コードの比較は、IntStr#stringBuilder0()
はるかに興味深いものです。生成されたコードの基本的なレイアウトはIntStr#integerToString()
両方の JVM で似ていましたが、Oracle JDK 8u5 は、Integer#toString(int)
コード内の一部の呼び出しをインライン化することに関して、より積極的であるように見えました。アセンブリの経験がほとんどない人でも、Java ソース コードとの明確な対応がありました。
ただし、のアセンブリ コードIntStr#stringBuilder0()
は根本的に異なっていました。Oracle JDK 8u5 によって生成されたコードは、再び Java ソース コードに直接関連していました。同じレイアウトであることは容易に認識できました。それどころか、OpenJDK 7 によって生成されたコードは、訓練されていない目 (私のように) にはほとんど認識できませんでした。コンストラクターでの配列のnew StringBuilder()
作成と同様に、呼び出しは一見削除されましたStringBuilder
。さらに、逆アセンブラー プラグインは、JDK 8 の場合ほど多くのソース コードへの参照を提供できませんでした。
これは、OpenJDK 7 でのより積極的な最適化パスの結果であるか、または特定の操作のために手書きの低レベル コードを挿入した結果である可能性が高いと思いますStringBuilder
。JVM 8 実装でこの最適化が行われない理由、またはInteger#toString(int)
JVM 7 で同じ最適化が実装されなかった理由がわかりません。JRE ソース コードの関連部分に詳しい人がこれらの質問に答える必要があると思います...