StringBuilderをstringと同じだけ作ると、どれほど遅いのか。
この間、以下のようなコードを見かけました。
StringBuilder strSql = new StringBuilder(); strSql.Append(@"SELECT * FROM ... WHERE ..."); // SQLは長いので省略。 this.ExecuteNonQuery(strSql.ToString());
フォームのメソッドに書かれていたのですが、フォーム自体がユーザコントロールになっていて、データベース接続の機能をすでに実装しているんですね。
なので、それにSQLを投げれば完了というすごくシンプルなものなのですが。
StringBuilderにする必要あったのかなって。
StringBuilderを必要とする理由
StringBuilder
は、 string
型が演算で毎回新しいインスタンスを生成したり破棄したりするのに対して、最初のインスタンスを使いまわすために使う、というものだと思います。
具体的には次のコード。
string s = ""; s = s + "a"; s = s + "b"; s = s + "c";
上記のコードでは、最終的に変数 s
に入るのは "abc"
という文字列ですが、その文字列を得るまでにインスタンスを生成する回数は7回です。
それに対し、
StringBuilder s = new StringBuilder(); s.Append("a"); s.Append("b"); s.Append("c");
上記のコードでも同様に、最終的に変数 s
に入るのは "abc"
という文字列ですが、インスタンスの生成は4回で済みます。
インスタンスの生成、という観点からみると、 string
型より StringBuilder
型のほうが同じインスタンスを使いまわすためオーバーヘッドが少ないと考えられます。
しかし。
最初のコードって、はじめに StringBuilder
型の変数を確保しているので、毎回インスタンスを生成していることになるんですよね。
これではstring型を直で触るのと変わらないのでは…。
ということで調べてみました。
検証コード
次の検証コードを用意しました。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { /// <summary> 出力用ダミー変数 </summary> static string d_out; /// <summary> /// メインエントリポイント /// </summary> static void Main(string[] args) { try { int count1; int count2; List<double> measuredList = new List<double>(); //------------------------------// // 入力 //------------------------------// do Console.WriteLine("1回の計測で確保を行う回数を指定してください..."); while (!int.TryParse(Console.ReadLine(), out count1)); do Console.WriteLine("計測を行う回数を指定してください..."); while (!int.TryParse(Console.ReadLine(), out count2)); //------------------------------// // 計測 string //------------------------------// measuredList.Clear(); Console.WriteLine("-------------------------------------------------------"); Console.WriteLine("stringクラスで計測します。"); GC.Collect(GC.MaxGeneration); for (int i = 0; i < count2; i++) { measuredList.Add(MeasureTime_String(count1)); Console.WriteLine((i + 1).ToString().PadLeft((int)Math.Log10(count2) + 1) + "回目 : " + measuredList[i]); } Console.WriteLine("平均 : " + measuredList.Average()); //------------------------------// // 計測 StringBuilder //------------------------------// measuredList.Clear(); Console.WriteLine("-------------------------------------------------------"); Console.WriteLine("StringBuilderクラスで計測します。"); GC.Collect(GC.MaxGeneration); for (int i = 0; i < count2; i++) { measuredList.Add(MeasureTime_StringBuilder(count1)); Console.WriteLine((i + 1).ToString().PadLeft((int)Math.Log10(count2) + 1) + "回目 : " + measuredList[i]); } Console.WriteLine("平均 : " + measuredList.Average()); //------------------------------// // 計測 StringBuilder + Append //------------------------------// measuredList.Clear(); Console.WriteLine("-------------------------------------------------------"); Console.WriteLine("StringBuilderクラス + Appendメソッドで計測します。"); GC.Collect(GC.MaxGeneration); for (int i = 0; i < count2; i++) { measuredList.Add(MeasureTime_StringBuilderAppend(count1)); Console.WriteLine((i + 1).ToString().PadLeft((int)Math.Log10(count2) + 1) + "回目 : " + measuredList[i]); } Console.WriteLine("平均 : " + measuredList.Average()); } finally { Console.WriteLine("何かキーを押してください..."); Console.ReadKey(); } } /// <summary> /// stringクラスを指定された回数確保し、その時間を計測します。 /// </summary> /// <param name="count">回数</param> /// <returns>時間(秒)</returns> static double MeasureTime_String(int count) { DateTime strT = DateTime.Now; for (int i = 0; i < count; i++) { string s = ""; s = s + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; Program.d_out = s.ToString(); } DateTime endT = DateTime.Now; return (endT - strT).TotalSeconds; } /// <summary> /// StringBuilderクラスを指定された回数確保し、その時間を計測します。 /// </summary> /// <param name="count">回数</param> /// <returns>時間(秒)</returns> static double MeasureTime_StringBuilder(int count) { DateTime strT = DateTime.Now; for (int i = 0; i < count; i++) { StringBuilder s = new StringBuilder("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); Program.d_out = s.ToString(); } DateTime endT = DateTime.Now; return (endT - strT).TotalSeconds; } /// <summary> /// StringBuilderクラスを指定された回数確保し、Appendメソッドで文字列を指定します。その時間を計測します。 /// </summary> /// <param name="count">回数</param> /// <returns>時間(秒)</returns> static double MeasureTime_StringBuilderAppend(int count) { DateTime strT = DateTime.Now; for (int i = 0; i < count; i++) { StringBuilder s = new StringBuilder(); s.Append("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); Program.d_out = s.ToString(); } DateTime endT = DateTime.Now; return (endT - strT).TotalSeconds; } } }
汚いとか言わない。
実際にやってみた。
実際に動かしました。スペックは以下の通り。
- CPU: Atom Z3775 (1.46GHz 4core)
- メモリ: 2GB
やっぱり、string型のほうがオーバーヘッドは少ないみたいですね。
追記 2017/02/26 実験に使ったスペックを記述していなかったので追記。