StringBuilder はどれだけ早いのだろうか?、実は大してかわりません

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 パターン

[code lang=”vb”]
”’ <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
[/code]

string.Format を使って、ぽちぽちと文字列をフォーマットしていきます。

■StringBuilder を使ったパターン

よくあるように、文字列の連結は StringBuilder を使えッ!!! ってことなので、使ってみたのですが、大して変わりません。もっと長い SQL の場合、効果があるんでしょうが…そんなに長い SQL を書くのはどうなの???ってことなのです。
[code lang=”vb”]
”’ <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
[/code]

■SqlCommand を使う

SqlCommand を使うと、最初に SQL 文を使うので文字列編集部分が極端に減ります。
これの場合は、20000 回の編集が 1 回になるわけですが、劇的に早くなる…はずなんですけど、結果は変わりません。

[code lang=”vb”]
”’ <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
[/code]

以上、こんな風に実験してみると普通に string を使っている限りはスピードに変化はありません、ってことです。
ただし、データベースアクセスに関しては、単純な SqlDataAdapter の呼び出しよりも、SqlCommand でプリコンパイル SQL を使ったほうが、10 倍以上早くなるので、件数が多い場合はパフォーマンスに注意が必要です。

カテゴリー: 開発, C# パーマリンク

StringBuilder はどれだけ早いのだろうか?、実は大してかわりません への5件のフィードバック

  1. はに丸 のコメント:

    根本的にいろいろ間違ってますね。
    まず、文字列連結有無にかかわらずバインド変数を使うべきです。
    それと、文字列連結のスピードは連結回数の総和が問題では無く、大きな文字列に連結することが問題となります。
    例えば、csv出力なんかで、1行のデータを作成する為に連結する場合はstring結合はまったく問題になりませんが、行そのものも縦方向に連結する際には問題となります。

    • masuda のコメント:

      I see. このネタは、文字列連結してSQL組み立てるとアカンからストアド使いましょう、のネタのつもりだったのですが、実際組み立ててみたら(ストアドを使ってないけど)「たいして時間が変わらなかった」ってネタ話です。その頃、「Java で StringBuilder を使わねばいかん」って話が盛り上がっていたので、別に += でも変わらん、っていうカウンターでもありましたが。

  2. 通りすがり のコメント:

    ストアドを使用したほうがいいのはそもそもVBA的な観点からではないでしょう・・

  3. 匿名 のコメント:

    この例ですと文字列の連結よりFormatの解釈処理に時間がかかって
    差が付かなかったのではないでしょうか?

    • masuda のコメント:

      .NET の GC を見ると、半分ぐらいは文字列処理に使っているので、その可能性は高いですね。const 文字列を使うよりも、enum で int 型を使ったほうが早い(メモリの解放とか文字列処理がない分だけ早い)ってのを、一度やってみたいと思ってます。

コメントは停止中です。