腐敗するメモリを制御せよ:Scripting.Dictionaryにおける「破棄」と「再利用」の境界線
長年、レガシーシステムの深淵で、数百万行のログ解析や大規模なデータ連携を自動化してきたエンジニアなら、一度は「謎のメモリ増大」に直面したことがあるはずだ。
VBScript(WSH)は、現代のメモリ管理が整った言語と比較すれば、極めて原始的なCOM(Component Object Model)のライフサイクルに依存している。特に、大量のデータを扱う際に我々が頼りにする `Scripting.Dictionary` は、一歩使い方を誤れば、プロセスを食いつぶし、OSのリソースを枯渇させる「メモリリークの温床」へと変貌する。
本稿では、シニアエンジニアが現場で直面する「`RemoveAll` と `Nothing` の使い分け」という、一見単純に見えて極めて奥深い、メモリ管理の真髄について解説する。
1. Scripting.Dictionary の内部構造と「見えないコスト」
まず理解すべきは、`Scripting.Dictionary` は VBScript 自体の機能ではなく、外部の ActiveX コントロール(Microsoft Scripting Runtime) であるという事実だ。
Dictionary オブジェクトが数万件のキーを保持するとき、内部ではハッシュテーブルが構築され、メモリ上に連続的あるいは断片的な領域が確保される。ここで重要なのは、「要素を削除すること」と「確保されたメモリ領域(Capacity)を解放すること」は、全く別の概念である という点だ。
RemoveAll の挙動:容量の温存
`objDict.RemoveAll` を実行したとき、Dictionary 内のキーと値のペアはすべて削除される。しかし、内部で確保されたハッシュテーブルのバケット(メモリ領域)は、そのまま維持されることが多い。
これは、次にデータが投入される際の再確保コストを抑えるための最適化だが、一方で「巨大な領域を掴んだまま、中身が空の状態でメモリを占有し続ける」という副作用を生む。
Nothing の挙動:参照カウントの減退
`Set objDict = Nothing` を実行したとき、行われるのは「参照カウント(Reference Count)」をデクリメントする操作のみだ。参照カウントがゼロになれば、COM サブシステムに対して「このオブジェクトは不要である」と通知が送られる。
しかし、OSがそのメモリを実際に物理メモリ(Working Set)から解放し、ヒープ領域を整理するまでには、タイムラグが生じる。ループ内で高速に `CreateObject` と `Nothing` を繰り返すと、解放が追いつかず、プロセス全体のメモリ使用量が右肩上がりに増大する現象――これが「偽のメモリリーク」の正体だ。
2. 極限の検証:メモリ増大のメカニズム
以下のコードは、あえて「誤った実装」をシミュレートし、その挙動を観察するための設計思想を示している。
' ==========================================================================
' 警告: このスクリプトはメモリ消費を意図的に増大させる検証用パターンである
' ==========================================================================
Option Explicit
Dim objWMI, objProcess, strProcessName, i
strProcessName = "wscript.exe" ' 実行環境に合わせて変更
Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
' メモリ使用量を表示する関数
Sub PrintMemoryUsage()
Dim colProcesses, objProc
Set colProcesses = objWMI.ExecQuery("Select WorkingSetSize from Win32_Process Where Name = '" & strProcessName & "'")
For Each objProc In colProcesses
' WorkingSetSize はバイト単位
WScript.Echo "Current Working Set: " & FormatNumber(objProc.WorkingSetSize / 1024 / 1024, 2) & " MB"
Next
End Sub
' --- パターンA: 毎回オブジェクトを生成して Nothing にする (危険なループ) ---
Sub BadPattern()
Dim i, objDict
WScript.Echo "--- Starting Bad Pattern ---"
For i = 1 To 100
' 毎回新しいオブジェクトを生成。解放が追いつかずメモリが膨らむリスク
Set objDict = CreateObject("Scripting.Dictionary")
For j = 1 To 100000
objDict.Add j, "Data_" & j
Next
' Nothing にしても、COMの解放プロセスが追いつかない場合がある
Set objDict = Nothing
If i Mod 10 = 0 Then
WScript.Echo "Iteration " & i
PrintMemoryUsage
End If
Next
End Sub
' --- パターンB: オブジェクトを再利用し RemoveAll を使う (安定パターン) ---
Sub GoodPattern()
Dim i, j, objDict
WScript.Echo "--- Starting Good Pattern ---"
Set objDict = CreateObject("Scripting.Dictionary")
For i = 1 To 100
' 中身だけを消去。メモリ領域は再利用されるため、割り当てのオーバーヘッドが少ない
objDict.RemoveAll
For j = 1 To 100000
objDict.Add j, "Data_" & j
Next
If i Mod 10 = 0 Then
WScript.Echo "Iteration " & i
PrintMemoryUsage
End If
Next
Set objDict = Nothing
End Sub
' 実行
' BadPattern
GoodPattern
アーキテクトの洞察
1. BadPattern (Create/Nothing 繰り返し):
新しいオブジェクトを生成するたびに、OSは新しいメモリ領域をヒープから切り出そうとする。`Nothing` による解放が OS のメモリ管理エンジンに受理される速度よりも、新しい要求が上回ると、プロセスは肥大化を続ける。これは、短寿命なオブジェクトを大量に生成する際の典型的な失敗である。
2. GoodPattern (RemoveAll 繰り返し):
一度確保したメモリ領域(ハッシュテーブルのバケット)を使い回す。メモリ消費量は、最大件数に達した時点で「プラトー(高原状態)」に達し、その後は一定となる。これは、「メモリの最大使用量を予測可能な状態に制御する」 という設計思想に基づいている。
3. 実戦における「黄金の使い分け」指針
現場での判断基準を、以下のマトリクスに集約する。
| シナリオ | 推奨される手法 | 理由 |
|---|---|---|
| ループ内でデータ件数が大きく変動する場合 | `RemoveAll` を基本とし、一定回数ごとに `Nothing` | `RemoveAll` でメモリの再利用性を高めつつ、極端な肥大化を防ぐために定期的にオブジェクトを破棄する。 |
| データ件数が一定、または増加傾向にある場合 | `RemoveAll` のみで使い回す | オブジェクトの生成・破棄に伴う CPU コストとメモリ断片化を最小限に抑える。 |
| メモリリソースが極めて限定的な環境 | `RemoveAll` + 定期的な `Nothing` + `DoEvents` (※注) | オブジェクトを破棄し、OSに制御を戻す隙間を作る。 |
*(※注: VBScriptには `DoEvents` は存在しないが、WSHの実行コンテキストにおいて、ループの合間に極めて短い `WScript.Sleep` を挟むことで、OS側のプロセス管理に猶予を与えることが実務上のテクニックとなる。)*
4. 結論:制御不能なシステムを構築しないために
VBScript はレガシーな技術であるが、それが現役である限り、我々はその「不完全さ」を理解し、制御しなければならない。
`Scripting.Dictionary` のメモリ管理において、重要なのは「空にする(Clear)」ことではなく、「リソースのライフサイクルをいかに予測可能にするか」 である。
- 頻繁な生成は避ける。
- `RemoveAll` による再利用を優先する。
- メモリの「天井」を設計段階で予測する。
これらを徹底することで、数千、数万のバッチ処理が走るエンタープライズ環境においても、安定した、堅牢な自動化システムを維持することが可能となる。
技術の真髄は、新しい言語を学ぶことだけでなく、古い言語の限界を知り、その限界の中でいかに美しく振る舞うかを設計することにある。
