【入門編】配列の動的再定義(ReDim)が遅い理由:メモリ再確保のコストを最小化するバッファ戦略 – Excel VBA解析バイブル

スポンサーリンク

こんにちは!マクロの記録から一歩抜け出して、「自分の手で自由自在にExcelを操りたい!」と熱意を燃やしているあなたへ。

今回は、VBA中級者への登竜門であり、実務で絶対に避けて通れない「配列のパフォーマンス」に関する極意をお伝えします。

「何万行もあるデータを処理すると、VBAが途中でフリーズしたように遅くなる…」
そんな悩みを抱えたことはありませんか? その原因の多くは、実は「ループの中での `ReDim` の乱用」にあります。

ここをクリアすれば、あなたの書くコードは見違えるほど軽快になり、「おっ、やるな!」と周りから一目置かれるエンジニアに一歩近づけます。それでは、メモリの裏側で何が起きているのか、一緒に覗いてみましょう!

1. なぜ `ReDim` をループ内で使うと遅いのか?

VBAで要素数が変動する配列を扱うとき、サイズを変更する `ReDim` ステートメントは非常に便利ですよね。
初心者グセとして、以下のようなコードを書いてしまいがちです。

‘ 【やってはいけないアンチパターン】
Dim arr() As String
Dim i As Long

For i = 1 to 10000
ReDim Preserve arr(i) ‘ ⚠️ループのたびにサイズを1つ増やす
arr(i) = “データ” & i
Next i

このコード、動くには動きますが、実務の大量データの前では完全に沈黙(フリーズ)します。 なぜでしょうか?

メモリの「引っ越し作業」という重労働

OSのメモリ管理の視点から、裏側で何が起きているかを図解してみましょう。

1. 初回 (`i = 1`): メモリ上に「1個分のスペース」を確保します。
2. 2回目 (`i = 2`): 「2個分のスペース」を新しく別の場所に確保し、さっきの1個分のデータをコピーし、古い場所を捨てます。
3. 3回目 (`i = 3`): 「3個分のスペース」をさらに別の場所に確保し、2個分のデータをコピーし……

お気づきでしょうか? `ReDim Preserve` を使うたびに、VBA(厳密にはWindowsのOS)は「もっと広い土地(メモリ)を探して、古い荷物を全部新しい土地に引っ越しさせる」という重労働を毎回させられているのです。

1万回これを繰り返すと、何回引っ越しをすることになるでしょうか? そりゃあパソコンも悲鳴を上げますよね。これが、ループ内 `ReDim` が遅い正体です。

2. 解決策:バッファ戦略(事前確保と切り詰め)というプロの技

この問題を解決するのが、現場のプロが使っている「バッファ戦略」です。

考え方はシンプル。

  • 「どうせこれくらい使うだろう」という大きめのサイズを、最初にまとめて確保しておく(事前確保)
  • ループが終わったあとに、実際に使ったサイズピッタリに縮める(切り詰め)

これだけで、メモリの引っ越し回数を劇的に減らすことができます。

実践! 最速のバッファリングコード

それでは、実際にどれほどスマートで高速なコードになるのか、サンプルを見てみましょう。ここでは「データの最大数は分からないけれど、せいぜい数万行程度」という想定で進めます。

Sub OptimizeArrayProcessing()
Dim ws As Worksheet
Set ws = ActiveSheet

‘ 最終行を取得(例としてA列のデータ数を見る)
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, “A”).End(xlUp).Row

If lastRow < 1 Then Exit Sub ' ① 【事前確保】最大の大きさを一気に確保(メモリの引っ越しをゼロにする) Dim dataBuffer() As String ReDim dataBuffer(1 To lastRow) Dim i As Long Dim validCount As Long validCount = 0 For i = 1 To lastRow ' 条件に合うデータだけを配列に詰めていく(例:空欄ではないもの) If ws.Cells(i, 1).Value <> “” Then
validCount = validCount + 1
dataBuffer(validCount) = ws.Cells(i, 1).Value
End If
Next i

‘ ② 【切り詰め】実際に有効だったデータサイズに一発でリサイズする
If validCount > 0 Then
ReDim Preserve dataBuffer(1 To validCount)
Else
Erase dataBuffer ‘ データが一件もない場合はクリア
End If

‘ 確認用メッセージ
MsgBox “処理完了! 有効データ数: ” & validCount & “件”, vbInformation

‘ ※この後、dataBufferをシートに書き戻したり、別の処理に渡したりします
End Sub

このコードの美しいポイント

1. ループ内の `ReDim` を完全に排除したこと。これにより、メモリの再割り当てコストが「たったの1回(またはゼロ)」になります。
2. 最後に `ReDim Preserve dataBuffer(1 To validCount)` で余分な空白を切り捨てているため、後続の処理で配列の大きさを気にする必要がなくなります。

3. 陥りやすいエラーと注意点

バッファ戦略を使う上で、初学者がハマりがちなポイントをいくつか押さえておきましょう。

  • エラー:「インデックスが有効範囲にありません」 (`Subscript out of range`)
  • 原因: 事前確保したサイズ(例: `1 To lastRow`)を超えてデータを格納しようとした場合に発生します。データの最大数が予測できない場合は、余裕を持った固定サイズ(例: 10万行分など)で大きめに確保するか、後述のコレクションやDictionaryの利用を検討してください。
  • `Preserve` の仕様上の制限
  • 多次元配列(例: `Dim arr(10, 10)`)の `ReDim Preserve` を使う場合、変更できるのは「一番最後の次元(列方向)」だけです。「行方向(1次元目)」のサイズを変えようとすると、コンパイルエラーや実行時エラーになります。この仕様も、配列設計において大きめのバッファを確保したくなる大きな理由の一つです。

まとめ:ここをクリアすればVBAの基本はバッチリ!

今回は、配列のパフォーマンスを最大化する「バッファ戦略」について解説しました。

  • NG: ループのたびに `ReDim Preserve arr(i)` を行う(メモリの引っ越し地獄)
  • OK: 最初に大きめのサイズを確保し、最後に必要なサイズへ切り詰める(バッファ戦略)

この考え方は、Excel VBAだけでなく、C#やVB.NET、Pythonといった他のプログラミング言語におけるメモリ管理やリスト構造の基本思想にも直結する、非常に強力なエンジニアリングの基礎です。

「動くだけのコード」から「速くて美しいコード」へ。
ここをクリアしたあなたなら、どんな大量データの処理も怖くないはずです。ぜひ、実際の業務のマクロに応用してみてくださいね。

あなたのVBAライフが、より快適で創造的なものになりますように!

タイトルとURLをコピーしました