2013-05-23 12 views
8

lead to believe "+"演算子を使用して1行に文字列を追加すると、StringBuilderを使用するのと同じくらい効果的でした(そして目にはかなり良い)。今日は、変数と文字列を追加していたLoggerに速度の問題がありましたが、 "+"演算子を使用していました。だから私は素早くtest caseを作ったのですが、驚いたことに、StringBuilderを使う方が早いことがわかりました!単線​​の文字列連結の速度の差

基本は、4つの異なる方法(以下に示す)を使用して、それぞれの追加回数に対して平均20回の実行を使用します。最速のアルゴリズムからの百分率差の(ミリ秒単位)

結果、回

 
               # of Appends 
          10^1 10^2 10^3 10^4  10^5  10^6  10^7 
StringBuilder(capacity) 0.65 1.25 2  11.7  117.65 1213.25 11570 
StringBuilder()   0.7  1.2  2.4  12.15 122  1253.7  12274.6 
"+" operator    0.75 0.95 2.35 12.35 127.2 1276.5  12483.4 
String.format    4.25 13.1 13.25 71.45 730.6 7217.15 - 

グラフ。

% Difference in String timings

私はそれは、各文字列の比較方法に違う、byte codeをチェックアウトしました。

ここでは、メソッドに使用しているものを示します。テストクラス全体を見ることができますhere

public static String stringSpeed1(float a, float b, float c, float x, float y, float z){ 
    StringBuilder sb = new StringBuilder(72).append("[").append(a).append(",").append(b).append(",").append(c).append("]["). 
      append(x).append(",").append(y).append(",").append(z).append("]"); 
    return sb.toString(); 
} 

public static String stringSpeed2(float a, float b, float c, float x, float y, float z){ 
    StringBuilder sb = new StringBuilder().append("[").append(a).append(",").append(b).append(",").append(c).append("]["). 
      append(x).append(",").append(y).append(",").append(z).append("]"); 
    return sb.toString(); 
} 

public static String stringSpeed3(float a, float b, float c, float x, float y, float z){ 
    return "["+a+","+b+","+c+"]["+x+","+y+","+z+"]"; 
} 

public static String stringSpeed4(float a, float b, float c, float x, float y, float z){ 
    return String.format("[%f,%f,%f][%f,%f,%f]", a,b,c,x,y,z); 
} 

私はfloat、ints、およびstringsで試しました。これらのすべては、ほぼ同じ時間差を示しています。

質問

  1. 「+」演算子は明らかに同じバイトコードになってきていない、と時間が最適とは非常に異なっています。だから何を与える?
  2. 100と10000の間のアルゴリズムの動作は非常に奇妙なので、誰でも説明がありますか?
+0

アルゴリズムの動作が100と.... ??? – ChrisCM

+0

固定、それは何らかの理由でそれをカットオフ – greedybuddha

+2

大きな質問は、それをバックアップするための研究とデータです。 +1 – syb0rg

答えて

3

Java言語仕様は、文字列の連結が行われる方法を指定しませんが、私はあなたのコンパイラが同等の何でもないことを疑う:あなたは「てjavap -cを使用することができます

new StringBuilder("["). 
    append(a). 
    append(","). 
    append(b). 
    append(","). 
    append(c). 
    append("]["). 
    append(x). 
    append(","). 
    append(y). 
    append(","). 
    append(z). 
    append("]"). 
    toString(); 

を... "あなたのクラスファイルを逆コンパイルし、これを確認してください。

メソッド間のランタイムの重大かつ反復的な違いを測定する場合は、ガベージコレクタが異なる時間に実行されていると思います。異なる初期容量を有するStringBuilderを作成することは、もちろん、いくらかの影響を及ぼし得るが、それは、例えば、必要な努力と比較して重要ではないはずである。浮動体をフォーマットします。

+0

私はコードを逆コンパイルしており、 "+"演算子はStringBuilder( "[")に対して試していませんでしたが、StringBuilder()とは異なります。 – greedybuddha

+0

更新:バイトコードは新しいStringBuilder ( "[").append ... – greedybuddha

+0

あなたの質問にjavapの出力を追加できますか? – jarnbjo

4

テストケースについて2つのことが気に入らなかった。まず、同じプロセス内ですべてのテストを実行しました。 「大」(私が知っている曖昧なもの)を扱うとき、しかしあなたのプロッセスが記憶と絡み合っているところを扱うときは、あなたの主な関心事です。常に別の実行でベンチマークを行うべきです。私たちがゴミ収集をスピンアップしたという事実だけが、以前の実行結果に影響を与えることができます。あなたの結果が私を混乱させるように因数分解しました。私がやったことは、それぞれを個別に走らせて、それを走らせた回数をゼロにしたことでした。私はまた、各担当者のタイミングを取って、いくつかの "担当者"のために走らせる。次に、実行に要した時間をミリ秒単位で表示します。

import java.util.Random; 

public class blah { 
    public static void main(String[] args){ 
    stringComp(); 
    } 

    private static void stringComp() { 
     int SIZE = 1000000; 
     int NUM_REPS = 5; 
     for(int j = 0; j < NUM_REPS; j++) { 
      Random r = new Random(); 
      float f; 
      long start = System.currentTimeMillis(); 
      for (int i=0;i<SIZE;i++){ 
       f = r.nextFloat(); 
       stringSpeed3(f,f,f,f,f,f); 
      } 
      System.out.print((System.currentTimeMillis() - start)); 
      System.out.print(", "); 
     } 
    } 

    public static String stringSpeed1(float a, float b, float c, float x, float y, float z){ 
     StringBuilder sb = new StringBuilder(72).append("[").append(a).append(",").append(b).append(",").append(c).append("]["). 
       append(x).append(",").append(y).append(",").append(z).append("]"); 
     return sb.toString(); 
    } 

    public static String stringSpeed2(float a, float b, float c, float x, float y, float z){ 
     StringBuilder sb = new StringBuilder().append("[").append(a).append(",").append(b).append(",").append(c).append("]["). 
       append(x).append(",").append(y).append(",").append(z).append("]"); 
     return sb.toString(); 
    } 

    public static String stringSpeed3(float a, float b, float c, float x, float y, float z){ 
     return "["+a+","+b+","+c+"]["+x+","+y+","+z+"]"; 
    } 

    public static String stringSpeed4(float a, float b, float c, float x, float y, float z){ 
     return String.format("[%f,%f,%f][%f,%f,%f]", a,b,c,x,y,z); 
    } 

} 

今私の結果:あなたは私の結果からわかるように

stringSpeed1(SIZE = 10000000): 11548, 11305, 11362, 11275, 11279 
stringSpeed2(SIZE = 10000000): 12386, 12217, 12242, 12237, 12156 
stringSpeed3(SIZE = 10000000): 12313, 12016, 12073, 12127, 12038 

stringSpeed1(SIZE = 1000000): 1292, 1164, 1170, 1168, 1172 
stringSpeed2(SIZE = 1000000): 1364, 1228, 1230, 1224, 1223 
stringSpeed3(SIZE = 1000000): 1370, 1229, 1227, 1229, 1230 

stringSpeed1(SIZE = 100000): 246, 115, 115, 116, 113 
stringSpeed2(SIZE = 100000): 255, 122, 123, 123, 121 
stringSpeed3(SIZE = 100000): 257, 123, 129, 124, 125 

stringSpeed1(SIZE = 10000): 113, 25, 14, 13, 13 
stringSpeed2(SIZE = 10000): 118, 23, 24, 16, 14 
stringSpeed3(SIZE = 10000): 120, 24, 16, 17, 14 

//This run SIZE is very interesting. 
stringSpeed1(SIZE = 1000): 55, 22, 8, 6, 4 
stringSpeed2(SIZE = 1000): 54, 23, 7, 4, 3 
stringSpeed3(SIZE = 1000): 58, 23, 7, 4, 4 

stringSpeed1(SIZE = 100): 6, 6, 6, 6, 6 
stringSpeed2(SIZE = 100): 6, 6, 5, 6, 6 
stirngSpeed3(SIZE = 100): 8, 6, 7, 6, 6 

、それぞれの連続した担当者が速くなる「中間の範囲」内にある値にここに私のコードです。これは、私が信じていることは、JVMが実行され、必要なメモリをつかむことによって説明されます。 「サイズ」が増えるにつれて、ガベージコレクタが放棄するメモリが余りにも多く、プロセスを元に戻すために、この効果は引き継ぐことができません。また、このような「反復的」なベンチマークを実行しているときに、ほとんどのプロセスがRAMではなくキャッシュのより低いレベルに存在する場合、プロセスは分岐予測子に対してもっと邪魔になります。これらは非常にスマートなので、あなたのプロセスがやっていることに気づくでしょう.JVMがこれを増幅すると思います。これはまた、初期ループの値がなぜ遅いのか、そしてこれをベンチマークする方法が貧弱な解決策であった理由を説明するのにも役立ちます。これは、「大」でない値の結果が歪んで奇妙に思える理由です。そして、あなたのベンチマークの "メモリフットプリント"が増加するにつれて、このブランチ予測は、あなたが追加していた大きな文字列がRAMにシフトされているよりも効果が少なくなります。

簡略化された結論:「大規模な」実行結果は合理的に有効であり、私の場合と似ています(結果はどのように得られたのかまだ完全に理解できていませんが、ただし、テストの性質上、小規模なテストの結果は有効ではありません。

+0

私はあなたがブランチプレディクタで何かに乗っていると思う、それは小さいランを歪めているgcだと思ったが、私は実際に1,2から3に落ちるのが大好き。大規模な実行に関しては、同じ効果を示していますが、メソッドは同等ではありません。 – greedybuddha

+0

ここでは多くの仮定を仮定していますが、その大部分は間違いがあります。より低いバッチサイズのスピードアップの最も明白な理由は、JVMがバイトコードを解釈することから始まり、数回の実行後にバイトコードがネイティブマシンコードにコンパイルされる(1回のペナルティを引き起こすが、その後の実行された後、JVMは、収集された実行時統計に基づいて異なる最適化戦略を使用して、バイトコードを何度か再コンパイルすることさえできます。 – jarnbjo

+0

また、自分の結果をどうやってどういう意味になっているのか分かりません。私はちょうどあなたが何をしたかと同様に各繰り返しの時間を合計しましたが、+ =を最後の担当者の数で割ったものです。 – greedybuddha