7

文字列プールに関するパフォーマンスベンチマークを試しています。ただし、結果は期待されていません。

3つの静的メソッドを作成しました

  • Perform0()メソッド...毎回新しいオブジェクトを作成します
  • Perform1()メソッド...文字列リテラル「テスト」
  • Perform2()メソッド...文字列定数式 "Te" + "st"

私の期待は(1.最速-> 3.最遅)でした

  1. 文字列プーリングによる「テスト」
  2. 文字列プーリングのために"Te"+ "st"ですが、+演算子のために1より少し遅くなります
  3. 文字列プーリングがないため、新しい文字列(..)。

しかし、ベンチマークは、「Te」+「st」が「Test」よりもわずかに速いことを示しています。

new String(): 141677000 ns 
"Test"      : 1148000 ns 
"Te"+"st"   : 1059000 ns

new String(): 141253000 ns
"Test"      : 1177000 ns
"Te"+"st"   : 1089000 ns

new String(): 142307000 ns
"Test"      : 1878000 ns
"Te"+"st"   : 1082000 ns

new String(): 142127000 ns
"Test"      : 1155000 ns
"Te"+"st"   : 1078000 ns
...

コードは次のとおりです。

import java.util.concurrent.TimeUnit;


public class StringPoolPerformance {

    public static long perform0() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = new String("Test");
        }
        return System.nanoTime()-start;
    }

    public static long perform1() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = "Test";
        }
        return System.nanoTime()-start;
    }

    public static long perform2() {
        long start = System.nanoTime();
        for (int i=0; i<1000000; i++) {
            String str = "Te"+"st";
        }
        return System.nanoTime()-start;
    }

    public static void main(String[] args) {
        long time0=0, time1=0, time2=0;
        for (int i=0; i<100; i++) {
            // result
            time0 += perform0();
            time1 += perform1();
            time2 += perform2();
        }

        System.out.println("new String(): " +  time0 + " ns");
        System.out.println("\"Test\"      : " + time1 + " ns");
        System.out.println("\"Te\"+\"st\"   : " + time2 + " ns");
    }
}

「Te」+「st」が「Test」よりも高速に動作する理由を誰かが説明できますか?JVMはここでいくつかの最適化を行っていますか?ありがとうございました。

4

5 に答える 5

10

"Te" + "st"はコンパイラ時の定数式であるため、実行時に単純に と同じ"Test"ように動作します。パフォーマンスの低下は、実行時ではなく、コンパイル時に発生します。

これは、次を使用してコンパイル済みのベンチマーク クラスを逆アセンブルすることで簡単に証明できますjavap -c StringPoolPerformance

public static long perform1();
  Code:
...
   7:   ldc #3; //int 1000000
   9:   if_icmpge   21
   12:  ldc #5; //String Test
   14:  astore_3
   15:  iinc    2, 1
...

public static long perform2();
  Code:
...
   7:   ldc #3; //int 1000000
   9:   if_icmpge   21
   12:  ldc #5; //String Test
   14:  astore_3
   15:  iinc    2, 1
...

メソッドのバイト コードはまったく同じです。これは、Java 言語仕様 15.18.1で指定されています。

式がコンパイル時の定数式 (§15.28) でない限り、String オブジェクトは新しく作成されます (§12.5)。

経験するベンチマークの違いは、おそらく典型的なばらつきによるものか、ベンチマークが完全ではないことが原因です。この質問を参照してください: Java で正しいマイクロベンチマークを作成するにはどうすればよいですか?

あなたが破るいくつかの注目すべきルール:

  1. テストカーネルの「ウォームアップ」反復の結果を破棄しません。
  2. GC ロギングが有効になっていません (特に、100 万個のオブジェクトを作成するテストの直後に常にperform1()実行されている場合に関連します)。
于 2012-08-22T20:50:08.230 に答える
3

おそらく、JIT コンパイラーが起動し、3 つ目はネイティブ コードを実行しています。連結がループの外に移動された可能性があります。変数が読み取られないため、連結が行われない可能性があります。おそらく違いはノイズであり、3 つのサンプルが偶然にも同じ方向を指しています。

「堅牢な Java ベンチマーク、第 1 回: 問題」では、Java のベンチマークが失敗する可能性がある多くの方法について説明しています。

ベンチマークは非常に困難です。明白なものから微妙なものまで、多くの要因が結果に影響を与える可能性があります。正確な結果を得るには、これらの問題の一部に対処するベンチマーク フレームワークを使用して、これらの問題を完全に把握する必要があります。パート 2に進んで、このような堅牢な Java ベンチマーク フレームワークについて学んでください。

JVM アーキテクチャがもたらす具体的な落とし穴を理解するまでは、Java コードのマイクロ ベンチマークから有益な情報が得られると期待しないでください。

あなたの目標が何であるかはわかりませんが、優れたプロファイラーの使い方を学び、実際のアプリケーションでそれを使用すると、通常、問題の行が本当に非効率の原因であるかどうかがわかります。また、コード変更の影響を測定できます。プロファイラーの学習に費やす時間は、マイクロ ベンチマークの記述とデバッグに費やす時間よりもおそらく有効です。

于 2012-08-22T20:51:41.947 に答える
0

回答を投稿して申し訳ありませんが、このベンチマークにどれほど欠陥があるかを示すためにこれをコメントに入れることはできませんでした。Linuxでは、順序を変更して取得しました:

順序は反抗的に重要です。

new String()   : 123328907 ns
"Test"         : 1153035 ns
"Te"+"st"      : 5389377 ns
"a"+"b"+"c"+"d": 1256918 ns
于 2012-08-22T21:11:42.183 に答える
0

まず第一に、次のことを知っておくとよいでしょう。

文字列を何度も連結している場合、たとえばループで言うと、文字列は不変であるため、新しい文字列が生成され続けることがわかります。javac コンパイラは内部的に StringBuffer を使用してこれを行います。たとえば、

String itemList = "";
 itemList=itemList + items[i].description;

ループで。

何が起こるかというと、ループ内で 2 つのオブジェクトが生成されます。1 つは StringBuffer です。

itemList=new StringBuffer().append(itemList).
      append(items[i].description).toString();

もう 1 つは、toString().` を介して itemList に割り当てられる文字列です。

ソース: http://thought-bytes.blogspot.com/2007/03/java-string-performance.html

これはあなたの場合には当てはまらないと思います。最初のパフォーマンス テストでは、常に新しいオブジェクトを作成するため、1000000String("Test")個のオブジェクトが作成されます。2 番目と 3 番目の例では、多数の参照先が指すオブジェクトが 1 つだけ作成されています。前に述べたように"Te"+"st"、コンパイラ時定数として扱われ、違いが小さすぎて「テスト」よりも高速であるとは言えません。

于 2012-08-22T21:00:29.173 に答える
0

Mark Peters の言う通り、2 つの String 定数は容赦なく連結されます。

これは、サイズに応じて String オブジェクトを連結するのに必要なコピー時間が原因です。

現在、これらはコンパイラによって StringBuffer/StringBuilder オブジェクトにコンパイルされており、.class ファイルを逆コンパイルすることで確認できます。

これらのクラスを確認する必要がありますが、StringBuilder または StringBuffer を String としてレンダリングすると、新しい String オブジェクトが作成されることに注意してください。

于 2012-08-22T21:02:44.467 に答える