Excel

マクロ高速化 - 文字列結合

文字列結合とメモリに関する豆知識

ある文字列変数に対し、文字列をどんどん結合していくような処理を作る時、
短い文字列を数十回結合する程度では、気にする必要もありませんが、
数千回、数万回と文字列結合を繰り返す場合には、
メモリを意識してコーディングしなければ、著しくパフォーマンスが低下します。

極端な例を挙げると↓

    strBind = String(32, Chr(32))

    strOutput = ""
    for i = 1 to 100000
        strOutput = strOutput & strBind
    next i

このようなコードを実行すると文字列結合の度に、
VBA が自動的にメモリ確保を繰り返してしまいます。
文字列結合の度に、メモリを確保していたのでは処理速度が遅くなるのは当たり前です。

では、どのようにすれば自動的に行われるメモリ確保を抑えることができるのでしょうか。
先ほどの例で挙げたコードを、次のように変更してみましょう。

    strBind = String(32, Chr(32))

    lngStrBuffer = 4096  ' 確保しているメモリの大きさ
    lngStrLength = 0     ' 文字列の実際の長さ
    lngAddLength = 0     ' 結合される文字列の長さ
    strOutput = String(lngStrBuffer, Chr(0))

    For i = 1 To 100000
        lngAddLength = Len(strBind)

    '-- バッファ管理
        If (lngStrBuffer < lngStrLength + lngAddLength) Then
            lngEnhanced = (lngAddLength \ 4096) * 4096 + 4096
            lngStrBuffer = lngStrBuffer + lngEnhanced
            strOutput = strOutput & String(lngEnhanced, Chr(0))
        End If

    '-- 文字列挿入
        Mid$(strOutput, lngStrLength + 1, lngEnhanced) = strBind
        lngStrLength = lngStrLength + lngEnhanced
    Next i

コード量は増えましたが、仕組みはいたって単純です。

1.あらかじめ決めておいた大きさのメモリを確保
2.メモリが不足するようであれば、大きめにメモリを確保
3.確保していたメモリに文字列を挿入

基本的に文字列結合部分は 2, 3 を繰り返しているだけです。
では、一つずつ見ていきましょう。

    lngStrBuffer = 4096  ' 確保しているメモリの大きさ
    lngStrLength = 0     ' 文字列の実際の長さ
    lngAddLength = 0     ' 結合される文字列の長さ
    strOutput = String(lngStrBuffer, Chr(0))

最初にあらかじめ 4096 Byte のメモリを確保しています。
4096 Byte という大きさに特に意味はないので適当に変更して下さい。

    If (lngStrBuffer < lngStrLength + lngAddLength) Then
        lngEnhanced = (lngAddLength \ 4096) * 4096 + 4096
        lngStrBuffer = lngStrBuffer + lngEnhanced
        strOutput = strOutput & String(lngEnhanced, Chr(0))
    End If

文字列が現在確保しているメモリサイズを超えるような事がないか確認し、
超えるようであれば事前にメモリを確保します。
この例では 4096 Byte 単位で拡張するように組まれていますが、
これは私の趣味です。別に 4096 Byte 単位できっちり拡張していく必要はないです。

    Mid$(strOutput, lngStrLength + 1, lngEnhanced) = strBind
    …(中略)…
    StrUnion = Left$(strOutput, lngStrLength)

ここで、あらかじめ確保していた領域に文字列を挿入しています。
最後に文字列の結合が終わった後、必要な長さだけ文字列を切り出します。

今回のコーディングがどの程度効果があるのかについては、次の表を参考にして下さい。
文字列の長さと、結合回数を変えながら計測しています。
上がコード修正前、下がコード修正後での実行時間(秒)です。
修正後の方が遅い場合は赤、速い場合は青と、色を塗り分けています。

  結合回数
5 10 50 100
長さ 5 0.21秒
0.49秒
0.22秒
0.53秒
0.39秒
0.51秒
0.42秒
0.54秒
10 0.36秒
0.55秒
0.40秒
0.57秒
0.61秒
0.60秒
0.82秒
0.66秒
50 1.69秒
1.08秒
2.21秒
1.13秒
6.94秒
1.30秒
12.72秒
2.27秒
100 4.33秒
1.77秒
6.64秒
1.85秒
25.10秒
2.87秒
48.21秒
8.11秒

変数初期化も含めて 100,000 回実行した時の結果です。
たかだか、文字数 100 、結合回数 100 でも効果は見てとれます。

ただし、文字列の長さが短く、結合回数が少ない場合、かえって遅くなるので、
ケースバイケースで適切な方法を選択して下さい。