【入門編】【動的配列の極意】ReDim Preserve のオーバーヘッドを最小化する配列自動拡張ロジックとSafeArray操作 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

こんにちは!VBScriptの世界へようこそ。
マクロの記録や簡単な自動化から一歩進んで、「もっと大規模で高速なスクリプトを書きたい!」と思ったとき、誰もが一度はぶつかる壁があります。それが「配列のサイズ変更(ReDim)」です。

今回は、VBScriptのパフォーマンスを極限まで引き上げるための奥義「動的配列の倍々(倍加)バッファリング」と、安全なSafeArray(セーフアレイ)操作の極意を伝授します。

ここをクリアすれば、あなたの書くVBScriptは見違えるほど洗練され、プロのアーキテクトが認める高速なコードに生まれ変わりますよ。それでは、一緒に本質を紐解いていきましょう!

—

1. なぜ `ReDim Preserve` は遅いのか?(VBScriptの裏側の世界)

VBScriptで要素数が未確定のデータを扱うとき、私たちは「動態配列」を使います。宣言時は `Dim arr()` としておき、後から大きさを変えるやつですね。

ここで頻繁に使われるのが、既存のデータを保持したままサイズを変える `ReDim Preserve` です。

‘ 【やってはいけないアンチパターン】
Dim arr()
ReDim arr(0)

For i = 1 to 10000
ReDim Preserve arr(i) ‘ 毎回サイズを1つずつ増やす
arr(i) = “データ_” & i
Next

一見、何の問題もなさそうに見えますよね。しかし、このコードは業務自動化の現場では絶対にやってはいけない「重罪」です。

メモリの引越し劇(オーバーヘッドの正体)

Windowsのメモリ管理において、配列は「連続したメモリ領域」に配置されなければなりません。
`ReDim Preserve arr(i)` を実行した瞬間、VBScript(背後で動くCOMとOS)は以下の裏方仕事をこなしています。

1. 新しい広さのメモリ領域を確保する(今のサイズ + 1)
2. 古いメモリ領域から新しい領域へ、全要素を1つずつコピーする
3. 古いメモリ領域を破棄する

これを1万回繰り返すとどうなるでしょうか? コピーの総量は「1 + 2 + 3 + … + 10,000」となり、O($N^2$) の計算量(爆発的な無駄処理)が発生します。スクリプトがフリーズしたように遅くなる原因はこれです。

—

2. 解決の鍵:「倍々(倍加)バッファリング」アルゴリズム

このオーバーヘッドを最小化する唯一にして最強の解が、「バッファサイズを倍々で拡張する(Geometric Growth)」手法です。

データが溢れそうになったら、現在の容量の「2倍」のメモリをあらかじめ確保します。これにより、メモリの引越し回数を極限まで減らす($O(N)$ の線形時間にする)ことができます。

概念イメージ(図解的表現)

  • 愚直な1つずつ拡張($O(N^2)$)

`[1] -> [1,2] -> [1,2,3] -> [1,2,3,4]` (毎回引越し!)

  • 倍々バッファリング($O(N)$)

`[容量2: 1, 2]
-> 溢れた! -> [容量4: 1, 2, 3, (空)]
-> 溢れた! -> [容量8: 1..7, (空)]` (引越し回数が激減!)

—

3. 実践!高速・安全な動的配列ラッパー実装

それでは、現場でそのままコピペして使える、実用的な「自動拡張配列クラス(風のルーチン)」を見てみましょう。VBScriptには真のクラスもありますが、今回は直感的に組み込めるサブルーチン形式で解説します。

以下のコードをコピーして、`.vbs` ファイルとして実行してみてください。

Option Explicit

‘ メイン処理:大量データを高速に処理するデモ
Sub Main
Dim objTimer
objTimer = Timer ‘ 実行時間計測用

Dim arrData, intCapacity, intCount
intCapacity = 4 ‘ 初期バッファサイズ(小さくてもOK)
intCount = 0 ‘ 実際に格納されているデータの数

‘ 動的配列の初期化
ReDim arrData(intCapacity – 1)

Dim i
‘ 5万件のデータを高速追加するテスト
For i = 1 To 50000
‘ 容量が足りなくなったら倍にする
If intCount >= intCapacity Then
intCapacity = intCapacity 2 ‘ ★ここでサイズを倍にする!
ReDim Preserve arrData(intCapacity – 1)
End If

‘ データを格納してカウントを進める
arrData(intCount) = “Item_” & i
intCount = intCount + 1
Next

‘ 最後に「実際に使ったデータ数」まで縮小する(トリミング)
If intCount > 0 Then
ReDim Preserve arrData(intCount – 1)
Else
arrData = Array() ‘ 空の場合
End If

WScript.Echo “処理完了! 格納件数: ” & UBound(arrData) + 1 & _
” / 処理時間: ” & (Timer – objTimer) & ” 秒”
End Sub

‘ 実行
Main

このコードのポイント

1. 初期バッファの確保: 最初から無駄に大きくせず、小さめのサイズ(例: 4)からスタートします。
2. 閾値判定 (`intCount >= intCapacity`): 配列の枠がいっぱいになった瞬間だけ `ReDim Preserve` を発動させます。
3. 最後のトリミング: 最後に余分に確保したメモリを削るため、一度だけ正確なサイズに縮小(`intCount – 1`)します。これによってメモリを無駄に汚しません。

このアプローチを取ることで、5万件程度のデータ処理なら一瞬(コンマ数秒)で完了するようになります。

—

4. 陥りやすい罠:多次元配列の「次元の呪い」

さて、基礎をマスターしたあなたへ、さらに一歩進んだ「知見」を共有します。
VBScriptでは多次元配列(例: `Dim arr(10, 10)`)も扱えますが、`ReDim Preserve` が使えるのは「一番最後の次元(右側の次元)」だけという鉄の掟があります。

Dim matrix()
ReDim matrix(2, 2)

‘ 【NG】これはエラーになります!
‘ ReDim Preserve matrix(5, 2)

‘ 【OK】一番最後の次元(列方向)しか拡張できません
ReDim Preserve matrix(2, 5)

もし行方向(左側の次元)を動的に拡張したい場合は、配列の配列(Jagged Array)を自作するか、データを1次元のフラットな配列で持ってインデックスを計算する(`index = y Width + x`)というプロ的アプローチが必要になります。

大規模なCSVやExcelの表データを扱う際は、この「多次元配列の制限」に十分注意してくださいね。

—

まとめ:ここをクリアすれば、VBScriptは怖くない!

  • `ReDim Preserve` の多用はメモリのコピー地獄($O(N^2)$)を引き起こすので厳禁。
  • データが増えたら容量を「2倍」にしていく倍々バッファリングで劇的に高速化する。
  • 最後不要になった余白のメモリは、最後に一度だけ縮小(トリミング)して綺麗に片付ける。
  • 多次元配列を拡張する際は「最後の次元しか増やせない」仕様に牙を剥かれないよう注意する。

いかがでしょうか?
一見古臭く見えるVBScriptですが、メモリのライフサイクルやOSの挙動を意識してコードを書くことで、現役のモダンな言語に負けない堅牢で高速な自動化スクリプトを作り上げることができます。

ここをクリアしたあなたなら、もう「マクロの記録」に頼るだけの初心者ではありません。自信を持って、日々の業務を華麗に自動化していきましょう!何か分からないことがあれば、いつでも先輩エンジニアに聞いてくださいね。

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