C#(.NET Framework)の文字列連結について

一般に文字列を+=で連結するのは遅いと言われています。事実そのとおりで、多量の連結を+=で行うと死ぬほど時間がかかります。例えば、以下のようなコードで示されることが多いでしょう。

// ベンチマーク用関数(10万回実行)
Func<Action, TimeSpan> bench = action =>
{
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < 100000; i++)
    {
        action();
    }
    return sw.Elapsed;
};
 
// StringBuilderの計測(最後のToStringを入れてませんが、あまり変わらないのでスルー)
var sb = new StringBuilder();
var sbTime = bench(() => sb.Append("hoge"));
// stringの+=での計測
var s = "";
var stringTime = bench(() => s += "hoge");
 
Console.WriteLine(sbTime); // 0.004sec
Console.WriteLine(stringTime); // 14.97sec

0.004secと15secでは話になりません(正確には、StringBuilderでは最後に文字列に変換するToStringを入れるべきですが、それでも1secは超えなくて差は歴然なので省略します)。ならば、文字列を連結する場合は、どのような時でもパフォーマンスのためにStringBuilderを使うべきでしょうか? 答えは違います。

// ILではひとつにまとまる
// IL_0001:  ldstr      "abcde"
var s = "a" + "b" + "c" + "d" + "e";

定数の連結はコンパイル時にひとまとめにされるので、StringBuilderを使うのは愚かな選択となります。この辺はILDASMで見ればわかるし、Reflectorでもひとまとめになって展開されているのが確認できます。では定数ではなく動的に値を返すものは?

static string Get()
{
    return DateTime.Now.ToString();
}
 
static void Main(string[] args)
{
    // IL_0031:  call       string [mscorlib]System.String::Concat(string[])
    var s = Get() + Get() + Get() + Get() + Get();
}

ILを見ると、s += Get(); s += Get(); みたいな展開のされかたにはならず、String.Concat(string[])が呼ばれることになります。よって、速度を心配してStringBuilderを使う必要は全くありません。測定してみましょう。(ちなみにs+=Get()だとString.Concat(string,string)が大量に呼ばれることになるのが遅い理由)

// benchとGetは上で使ったのと同じものを流用
var sbTime = bench(() =>
{
    var sb = new StringBuilder();
    sb.Append(Get())
      .Append(Get())
      .Append(Get())
      .Append(Get())
      .Append(Get());
    var s = sb.ToString();
});
 
var stringTime = bench(() =>
{
    var s = Get() + Get() + Get() + Get() + Get();
});
 
Console.WriteLine(sbTime); // 0.65sec
Console.WriteLine(stringTime); // 0.64sec

速度はほとんど変わりません。StringBuilderとString.Concatでは処理の中身は結構違いますが、速度変わらないのならどっちでもいいよね。なら、記述しやすいほうを選ぶのが良いでしょう。妄信的にパフォーマンスのためにStringBuilder!とか思っている人は、少し考え直してみてください。そんなの当たり前だろ常識的に考えて、と思っていた時期が私にもありました……。世の中は存外StringBuilder神話に溢れているかもですよ? いやほんと。あとC#なら逐語的リテラル文字列もお忘れなく。

Comment (2)

菊池 : (01/08 13:50)

んー、短い文字列の連結では String.Concat / StringBuilder.Appendの差は殆ど出ないという結果しか出ていないかと。(sb.Append(Get()).Append(Get())のパターンでは直前でnewしているから短い文字列しか扱っていない)
「差が殆ど出ないなら多少無駄でも StringBuilder.Append 使っとけ」を否定するには根拠薄い気がします。

neuecc : (01/09 02:25)

いやあ、趣旨が+を使ってもStringBuilderと差は出ない、だったのです。
そして結論が「+のほうがスッキリするよね?」なので、StringBuilderを否定した割に、
根拠が丸っきり主観的、な上にパフォーマンス面の話は完全スルー(というか考えてなかった)
なのが片手落ちでした……。

書いた時の心情が、何やるにもStringBuilderだらけのコードを見て
if(isHoge)
{
return true;
}
else
{
return false;
}
を見て
return isHoge
でいいぢゃん、といった感じだったので、思わずStringBuilderに敵意が。

Name
WebSite(option)
Comment

Trackback(1) | http://neue.cc/2010/01/07_234.html/trackback

C#で文字コードから文字列を生成する « 空談録 : (01/10 22:25)

[…] とにかく+=を改造します これはStringBuilder クラス (System.Text)とかneue cc – C#(.NET Framework)の文字列連結についてを参考にしながら書き換えてみます […]

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Developer Technologies(C#)

April 2011
|
July 2021

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc