C#(.NET Framework)の文字列連結について
- C# - 10.01/07
一般に文字列を+=で連結するのは遅いと言われています。事実そのとおりで、多量の連結を+=で行うと死ぬほど時間がかかります。例えば、以下のようなコードで示されることが多いでしょう。
// ベンチマーク用関数(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に敵意が。