C#の開発における落とし穴~文字列編~
ごきげんよう、皆様。
技術担当の大林です。
前回に引き続き、C#の開発における落とし穴、所謂うっかりやらかしたり勘違いしやすい部分について少し書こうと思います。
※当記事はVisualStudioでの開発を想定して執筆しております。
開発環境次第では当てはまらない事もある事を、あらかじめご了承ください。
さて、今回は文字列編集について触れようと思います。
文字列編集というと、VB.NET等の言語では「String型は遅いのでそのまま追加していく形で使うな!StringBuilderを使え!」といった事がよく言われていました。
業務やっていた人はコード規約等に書かれている所を見たことが有るかもしれませんね。
さて、今回はこちらが本当かどうか、ついでに他の文字列編集も一緒に見て速度を確認していこうと思います。
というわけで、まずはそのままstring+string+…のような形で試してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int max = 10000000; Stopwatch sw1 = new Stopwatch(); sw1.Start(); string plane = ""; for (int i = 1; i < max; i++) { plane = ""; plane += "テスト"; plane += i; plane += "回目"; } sw1.Stop(); Console.WriteLine(string.Format("そのままstringを足す:{0}",sw1.Elapsed.TotalMilliseconds)); |
出力:「そのままstringを足す:1520.552」
1000万回実行して大体1.5秒、そんなに目くじら立てるほどのものではなさそうですね。
さて、本題のStringBuilderに入る前に少し寄り道してみます。
C#では、下記のように文字列の中に{0}のような形で埋め込んで引数で代替させるようなstring.Formatという関数があります。
こちらの方も少し試して見ましょう。
1 2 3 4 5 6 7 8 9 | Stopwatch sw2 = new Stopwatch(); sw2.Start(); string format = ""; for (int i = 1; i < max; i++) { format = string.Format("テスト{0}回目", i); } sw2.Stop(); Console.WriteLine(string.Format("string.Formatで作成:{0}", sw2.Elapsed.TotalMilliseconds)); |
出力:「string.Formatで作成:1894.4301」
1000万回実行して大体1.9秒、遅くなってしまいました。
多分、置き換え元の文字列解析やそれにあわせた複数回の文字列置き換え等が中で走ってると思われるので、ある程度仕方ない感はあります。
さて、最後に本題のStringBuilderに挑戦です。
VB.NET等の事を考えるなら、何倍も早くなるはずですが……?
1 2 3 4 5 6 7 8 9 10 11 12 | Stopwatch sw3 = new Stopwatch(); sw3.Start(); StringBuilder sb = new StringBuilder(); string sbtext = ""; for (int i = 1; i < max; i++) { sb = new StringBuilder(); sb.Append("テスト").Append(i).Append("回目"); sbtext = sb.ToString(); } sw3.Stop(); Console.WriteLine(string.Format("StringBuilderで作成:{0}", sw3.Elapsed.TotalMilliseconds)); |
出力:「StringBuilderで作成:1520.2493」
はい、という訳でstring+string+…とした場合となんら変わらない結果になりました。
なので、c#ではあまりこの辺りを意識しなくてもいいのかもしれません。
ところで、StringBuilderは文字数がcapacityに達すると自動的にStringBuilderで確保している領域を拡張するという動作があります。
今回のコードでは文字列が短いため拡張起こらなさそうですが、ひょっとしたら毎回文字列の上限の拡張等が行われていて遅くなっているのかもしれないので、念のためcapacity指定した場合も試してみます。
1 2 3 4 5 6 7 8 9 10 | Stopwatch sw4 = new Stopwatch(); sw4.Start(); for (int i = 1; i < max; i++) { sb = new StringBuilder(100,100); sb.Append("テスト").Append(i).Append("回目"); sbtext = sb.ToString(); } sw4.Stop(); Console.WriteLine(string.Format("StringBuilder(サイズ指定)で作成:{0}", sw4.Elapsed.TotalMilliseconds)); |
出力:「StringBuilder(サイズ指定)で作成:1660.583」
指定したら逆に少し遅くなってしまいました。
長い文字列等を扱わない場合、このあたりは設定しないほうが良いかもしれません。
ちなみに一応、下記のようにStringBuilderをnewするのではなく、Clearで使いまわす形にすれば、確かに少し速く出来たりはします。
1 2 3 4 5 6 7 8 9 10 | Stopwatch sw5 = new Stopwatch(); sw5.Start(); for (int i = 1; i < max; i++) { sb.Clear(); sb.Append("テスト").Append(i).Append("回目"); sbtext = sb.ToString(); } sw5.Stop(); Console.WriteLine(string.Format("StringBuilderで作成(newしない):{0}", sw5.Elapsed.TotalMilliseconds)); |
出力「StringBuilderで作成(newしない):1356.4736」
ただ、こちらの方法を行う場合、StringBuilderをcapacity等の変更無しで使いまわす形になるので、もしこれによる高速化を行う場合、使いまわすStringBuilderごとに対象を決めてきっちり設計する必要がありますのでご注意を。
では!
見ていただき、ありがとうございました!