76

整数プリミティブを使用して文字列に変換することに関する少しの議論を調査しているときに、次のJMHマイクロベンチマークを作成しました。"" + nInteger.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のクラスを並べて比較すると、注目に値するものは何もありません。多くの表面的な変更とドキュメントの変更は別として、は符号なし整数をサポートするようになり、 とより多くのコードを共有するためにわずかにリファクタリングされました。これらの変更は、 で使用されるコード パスに影響を与えるものではないようですが、見落としがあるかもしれません。StringBuilderIntegersrc.zipIntegerStringBuilderStringBufferStringBuilder#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 ソース コードの関連部分に詳しい人がこれらの質問に答える必要があると思います...

4

2 に答える 2

5

CompileThresholdこれは、バイト コードが JIT によってマシン コードにコンパイルされるタイミングを制御するフラグに関係していると思います。

Oracle JDK には、http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.htmlのドキュメントとして、デフォルトのカウントが 10,000 あります。

OpenJDK では、このフラグに関する最新のドキュメントが見つかりませんでした。ただし、一部のメール スレッドは、はるかに低いしきい値を示唆しています: http://mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2010-November/004239.html

-XX:+UseCompressedStringsまた、や などの Oracle JDK フラグをオン/オフにしてみてください-XX:+OptimizeStringConcat。ただし、OpenJDK でこれらのフラグがデフォルトでオンになっているかどうかはわかりません。誰か提案してください。

実行できる実験の 1 つは、最初にプログラムを何度も (たとえば 30,000 ループ) 実行し、System.gc() を実行してから、パフォーマンスを調べることです。私は彼らが同じものをもたらすと信じています。

そして、あなたのGC設定も同じだと思います。そうしないと、多くのオブジェクトを割り当てることになり、GC が実行時間の大部分を占める可能性があります。

于 2014-05-20T10:36:16.757 に答える