SqlCommand や DataTable を使うときに文字列をたくさん使うのですが、果たして世間一般(?)で言われているほど、String は遅く、StringBuilder は早いのでしょうか?というベンチマークです。
非常に長い場合は StringBuilder を使うほうが良いのですが、SqlCommand などに渡す SQL 文程度(長くても 5000 文字ぐらい)は、どうなのでしょうか?ということです。
■結論
結論から言えば、大して変わりません。以下は 20,000 件のデータを廻したときの結果です。
avg. Normal 7.54 sec
avg. StirngBuilder 7.22 sec
avg. SqlCommand 7.28 sec
違いは 2,3 % ぐらいしかないので誤差の範囲ですね。
以下は、ベンチマーク用の実験コード
■通常の String パターン
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ''' <summary> ''' 1.通常の string の連結で作成 ''' </summary> ''' <remarks></remarks> Public Sub TestNormal() Dim s As String = toMD5(DateTime.Now.ToString()) Dim cn As New SqlConnection( "" ) For j As Integer = 0 To MAX - 1 Dim sql As String = "" ' よくある string.Format を使った作り方 sql += "SELECT * FROM DUAL " sql += " WHERE 1 = 1 " sql += String .Format( " AND col0 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col1 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col2 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col3 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col4 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col5 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col6 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col7 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col8 = '{0}' " , s) : s = toMD5(s) sql += String .Format( " AND col9 = '{0}' " , s) : s = toMD5(s) Dim dt As New DataTable Dim da As New SqlDataAdapter(sql, cn) 'dummy Next End Sub |
string.Format を使って、ぽちぽちと文字列をフォーマットしていきます。
■StringBuilder を使ったパターン
よくあるように、文字列の連結は StringBuilder を使えッ!!! ってことなので、使ってみたのですが、大して変わりません。もっと長い SQL の場合、効果があるんでしょうが…そんなに長い SQL を書くのはどうなの???ってことなのです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ''' <summary> ''' 2.StringBuilder を使う ''' </summary> ''' <remarks></remarks> Public Sub TestStringBuilder() Dim s As String = toMD5(DateTime.Now.ToString()) Dim cn As New SqlConnection( "" ) For j As Integer = 0 To MAX - 1 Dim sql As String = "" ' よくある StringBuilder を使った作り方 Dim sb As New System.Text.StringBuilder( "" ) sb.Append( "SELECT * FROM DUAL " ) sb.Append( " WHERE 1 = 1 " ) sb.AppendFormat( " AND col0 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col1 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col2 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col3 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col4 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col5 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col6 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col7 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col8 = '{0}' " , s) : s = toMD5(s) sb.AppendFormat( " AND col9 = '{0}' " , s) : s = toMD5(s) Dim dt As New DataTable Dim da As New SqlDataAdapter(sql, cn) 'dummy Next End Sub |
■SqlCommand を使う
SqlCommand を使うと、最初に SQL 文を使うので文字列編集部分が極端に減ります。
これの場合は、20000 回の編集が 1 回になるわけですが、劇的に早くなる…はずなんですけど、結果は変わりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | ''' <summary> ''' SqlCommand で Const/Dim を使う ''' </summary> ''' <remarks></remarks> Public Sub TestConst() Dim s As String = toMD5(DateTime.Now.ToString()) Dim cn As New SqlConnection( "" ) ' ここは dim でも同じ Const sql As String = _ "SELECT * FROM DUAL0 " + _ " WHERE 1 = 1 " + _ " AND col0 = @col0 " + _ " AND col1 = @col1 " + _ " AND col2 = @col2 " + _ " AND col3 = @col3 " + _ " AND col4 = @col4 " + _ " AND col5 = @col5 " + _ " AND col6 = @col6 " + _ " AND col7 = @col7 " + _ " AND col8 = @col8 " + _ " AND col9 = @col9 ; " Dim cmd As New SqlCommand(sql, cn) cmd.Parameters.Add( New SqlParameter( "@col0" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col1" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col2" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col3" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col4" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col5" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col6" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col7" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col8" , Nothing )) cmd.Parameters.Add( New SqlParameter( "@col9" , Nothing )) For j As Integer = 0 To max - 1 cmd.Parameters( "@col0" ).Value = s : s = toMD5(s) cmd.Parameters( "@col1" ).Value = s : s = toMD5(s) cmd.Parameters( "@col2" ).Value = s : s = toMD5(s) cmd.Parameters( "@col3" ).Value = s : s = toMD5(s) cmd.Parameters( "@col4" ).Value = s : s = toMD5(s) cmd.Parameters( "@col5" ).Value = s : s = toMD5(s) cmd.Parameters( "@col6" ).Value = s : s = toMD5(s) cmd.Parameters( "@col7" ).Value = s : s = toMD5(s) cmd.Parameters( "@col8" ).Value = s : s = toMD5(s) cmd.Parameters( "@col9" ).Value = s : s = toMD5(s) Dim dt As New DataTable Dim da As New SqlDataAdapter(cmd) 'dummy Next End Sub |
以上、こんな風に実験してみると普通に string を使っている限りはスピードに変化はありません、ってことです。
ただし、データベースアクセスに関しては、単純な SqlDataAdapter の呼び出しよりも、SqlCommand でプリコンパイル SQL を使ったほうが、10 倍以上早くなるので、件数が多い場合はパフォーマンスに注意が必要です。
根本的にいろいろ間違ってますね。
まず、文字列連結有無にかかわらずバインド変数を使うべきです。
それと、文字列連結のスピードは連結回数の総和が問題では無く、大きな文字列に連結することが問題となります。
例えば、csv出力なんかで、1行のデータを作成する為に連結する場合はstring結合はまったく問題になりませんが、行そのものも縦方向に連結する際には問題となります。
I see. このネタは、文字列連結してSQL組み立てるとアカンからストアド使いましょう、のネタのつもりだったのですが、実際組み立ててみたら(ストアドを使ってないけど)「たいして時間が変わらなかった」ってネタ話です。その頃、「Java で StringBuilder を使わねばいかん」って話が盛り上がっていたので、別に += でも変わらん、っていうカウンターでもありましたが。
ストアドを使用したほうがいいのはそもそもVBA的な観点からではないでしょう・・
この例ですと文字列の連結よりFormatの解釈処理に時間がかかって
差が付かなかったのではないでしょうか?
.NET の GC を見ると、半分ぐらいは文字列処理に使っているので、その可能性は高いですね。const 文字列を使うよりも、enum で int 型を使ったほうが早い(メモリの解放とか文字列処理がない分だけ早い)ってのを、一度やってみたいと思ってます。