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

''' <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 を書くのはどうなの???ってことなのです。

''' <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 回になるわけですが、劇的に早くなる…はずなんですけど、結果は変わりません。

''' <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 倍以上早くなるので、件数が多い場合はパフォーマンスに注意が必要です。

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

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

  1. はに丸 のコメント:

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

    • masuda のコメント:

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

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

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

  3. 匿名 のコメント:

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

    • masuda のコメント:

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

コメントは停止中です。