文字列連結のスピード

via オレンジニュース

同じ事をJavaで試してみました。
環境は、

  • CPU : Pentium 4 1.70GHz
  • RAM : 1024MB
  • JVM : J2RE 1.5.0(ヒープサイズはデフォルト)

+=演算子による連結、String#concat()メソッドによる連結(あまり一般的ではないと思いますが・・・)、定石のStringBufferクラス、さらにJ2SE 5.0で加わったStringBuilderクラスも試してみました。

ソースはこれ。J2SE 5.0以降対応です。5.0の新しいSystem.nanoTime()はこういうベンチマークで便利なんですが、DateFormat等で整形できないのがなんとも・・・。

public class StringConcatenateTest
{

    public static void main(String[] args)
    {
        int times = 1000;
        System.out.println(times + " times loop.");

        // +=
        long start = System.nanoTime();
        String str = "";
        for (int i = 0; i < times; i++)
        {
            str += i;
        }
        System.out.println("String +=       : " + getNanoTimeString(System.nanoTime() - start));

        // String#concat()
        long start_concat = System.nanoTime();
        String str_concat = "";
        for (int i = 0; i < times; i++)
        {
            str_concat.concat("" + i);
        }
        System.out.println("String#concat() : " + getNanoTimeString(System.nanoTime() - start_concat));

        // StringBuffer
        long start_stringbuffer = System.nanoTime();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < times; i++)
        {
            buffer.append(i);
        }
        System.out.println("StringBuffer    : " + getNanoTimeString(System.nanoTime() - start_stringbuffer));

    
        // StringBuilder
        long start_stringbuilder = System.nanoTime();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < times; i++)
        {
            builder.append(i);
        }
        System.out.println("StringBuilder   : " + getNanoTimeString(System.nanoTime() - start_stringbuilder));
    
    }
    
    private static String getNanoTimeString(long nanoTime)
    {
        long t = nanoTime;
        
        long nsec = t % 1000;
        t /= 1000;
        long usec = t % 1000;
        t /= 1000;
        long msec = t % 1000;
        t /= 1000;
        long sec = t % 60;
        t /= 60;
        long minute = t % 60;
        t /= 60;
        long hour = t;

        return String.format("%02d:%02d:%02d.%03d%03d%03d", hour, minute, sec, msec, usec, nsec);
      } 

}

まずは1000回のループ。

1000 times loop.
String +=       : 00:00:00.038828399
String#concat() : 00:00:00.012665577
StringBuffer    : 00:00:00.000677181
StringBuilder   : 00:00:00.000211759

1万回では、

10000 times loop.
String +=       : 00:00:05.104651569
String#concat() : 00:00:00.029140576
StringBuffer    : 00:00:00.005066845
StringBuilder   : 00:00:00.002816839

10万回

100000 times loop.
String +=       : (話にならないくらい遅い)
String#concat() : 00:00:00.112415405
StringBuffer    : 00:00:00.108727785
StringBuilder   : 00:00:00.101025968

100万回

1000000 times loop.
String +=       : (話にならないくらい遅い)
String#concat() : 00:00:00.873067666
StringBuffer    : 00:00:00.669204047
StringBuilder   : 00:00:00.593941409

1000万回

10000000 times loop.
String +=       : (話にならないくらい遅い)
String#concat() : 00:00:08.593192710
StringBuffer    : (java.lang.OutOfMemoryErrorで実行できず)
StringBuilder   : (java.lang.OutOfMemoryErrorで実行できず)

結論。StringBuilder激速。これからはStringBuilder。ただし、スレッドセーフでない点には気をつけましょう。StringBufferとStringBuilderの関係は、VectorArrayList、HashtableとHashMapの関係と同じということですね。

あと、C#のStringBuilderはスレッドセーフなので、混同しないように。

String#concat()が意外と速いのは驚きでした。メモリ消費も少そうな感じですね。id:hyperash:20051205で修正版を書きました)

追記

こんな情報も見つけてしまいました。またまた悩ましい・・・。

個人的には、このような些細なパフォーマンスアップテクニックを身につけるよりは、ソースコードの可読性を向上させ、パフォーマンスチューニングはコンパイラに任せるというスタンスがやはり正しいでしょうし、特に最近はコンパイラに任せきりでも十分なような気がします(状況によりますが・・ ;-<

その「状況による」チューニングばかりしている今日この頃 :-(

2005/12/04 追記

コメント欄で指摘を頂きまして、String#concat()の使い方を激しく間違っておりました。ベンチマークやり直しですね。

2005/12/05 追記

id:hyperash:20051205で、ベンチマークをやり直した結果を書きました。