【入門編】【上級プロフェッショナル向け】大量テキストのFSO読み込み時に文字列結合(&)によるヒープ枯渇・低速化を回避する「配列Joinバッファリング」最適化 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

「なぜか遅い」を卒業する。VBScriptで大量データを高速に捌く「配列Joinバッファリング」の極意

こんにちは。現場の自動化を支えるエンジニアの皆さん、今日もコードを書いていますか?

マクロの記録から一歩踏み出し、自分でスクリプトを書き始めた皆さんが必ずと言っていいほどぶつかる壁があります。それは、「最初は速かったのに、処理するファイルが大きくなると、急に動きが重くなり、最悪の場合、スクリプトが止まってしまう」という現象です。

「コードは間違っていないはずなのに、なぜ?」

その答えは、プログラムの書き方ではなく、「コンピュータのメモリ(記憶領域)の仕組み」に隠されています。今日は、VBScriptで大量のテキストファイルを扱う際に、プロが必ず実践する「メモリを賢く使うテクニック」を伝授します。

ここをクリアすれば、あなたの書くスクリプトは「動くだけのツール」から「プロ仕様の堅牢なシステム」へと進化しますよ。

1. 犯人は「文字列結合(&)」の罠

まず、初心者がやりがちな「遅くなるコード」を見てみましょう。

【アンチパターン】文字列を `&` で継ぎ足していく方法

' ⚠️ 注意:これは大量データでは非常に遅くなります!
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.OpenTextFile("large_data.txt", 1)

Dim allText
allText = ""

Do Until ts.AtEndOfStream
    ' 1行読み込んで、既存の文字列に「&」で結合していく
    allText = allText & ts.ReadLine & vbCrLf
Loop

ts.Close
WScript.Echo "読み込み完了"

一見、とてもシンプルで分かりやすいですよね。しかし、この `allText = allText & …` という一行が、大量のデータ(数MB〜数百MB)を扱う際に、「地獄の低速化」を引き起こします。

なぜ遅くなるのか?(メモリの再割り当て問題)

ここが本質的な部分です。VBScriptにおける「文字列」は、一度作られるとサイズを変更できない「不変(Immutable)」に近い性質を持っています。

あなたが `&` を使って文字列を結合しようとする時、コンピュータの内部では以下のことが起きています。

1. 現在の `allText` の内容をコピーするために、新しいメモリ領域を確保する。
2. 新しい文字列をそこに書き込む。
3. 古いメモリ領域を解放する。

これを1行ごとに繰り返すとどうなるでしょうか?
10万行のファイルがあれば、10万回の「メモリ確保」と「全データのコピー」が発生します。行数が増えるほど、コピーする対象(前の文字列)も巨大になっていくため、処理時間は指数関数的に増大していきます。これが、メモリを食いつぶし(ヒープ枯渇)、動作を重くする正体です。

2. 救世主「配列Joinバッファリング」

では、プロはどうやってこの問題を解決するのか。
答えは、「文字列を直接つなげるのではなく、一旦『箱(配列)』に放り込み、最後にまとめてガッチャンコする」という手法です。

これを「配列Joinバッファリング」と呼びます。

【プロフェッショナル・パターン】配列とJoinを使った高速化

' 🚀 最適化されたコード:配列Joinバッファリング
Option Explicit ' 変数宣言を強制(バグ防止の鉄則!)

Dim fso, ts, filePath
Dim lineData, dataArray
Dim count, totalLines

filePath = "large_data.txt"

Set fso = CreateObject("Scripting.FileSystemObject")

' ファイルが存在するかチェック(エラー回避の基本)
If Not fso.FileExists(filePath) Then
    WScript.Echo "エラー: ファイルが見つかりません。"
    WScript.Quit
End If

Set ts = fso.OpenTextFile(filePath, 1)

' --- ここからが最適化の核心 ---

' 1. まず、データを格納するための「配列」を用意する
' 最初は適当なサイズで確保(ReDimで後から増やせる)
ReDim dataArray(0)
count = 0

Do Until ts.AtEndOfStream
    ' 2. データを配列の要素として格納していく
    ' ※配列のサイズが足りなくなったら ReDim Preserve で拡張
    ' ※ ただし、頻繁な ReDim は重いため、本来は大きめの塊で増やすのが理想
    dataArray(count) = ts.ReadLine
    count = count + 1
    
    ' 配列の容量が足りなくなったら拡張する(効率的な拡張)
    If count > UBound(dataArray) Then
        ReDim Preserve dataArray(count + 1000) ' 1000行分まとめて確保
    End If
Loop

ts.Close

' 3. 【重要】最後に Join 関数を使って一括で文字列に変換する
' Join関数は、メモリを一度だけ計算して一気に結合するため、劇的に速い!
Dim finalResult
finalResult = Join(dataArray, vbCrLf)

' --- ここまで ---

WScript.Echo "読み込み完了。取得行数: " & count
' WScript.Echo finalResult ' 結果を確認する場合

3. 技術解説:なぜこの方法が「最強」なのか

この手法がなぜ速いのか、そのメカニズムを分解してみましょう。

① 配列は「参照」の集まりである

配列(Array)に文字列を入れるとき、コンピュータは文字列そのものを配列の中にコピーして詰め込むのではなく、「文字列がメモリのどこにあるか」という情報(ポインタ/参照)を並べているだけです。
そのため、`&` で結合する時のように「毎回全データをコピーする」という無駄な動きが発生しません。

② `Join` 関数の魔法

`Join` 関数は、VBScriptの内部エンジンが高度に最適化された処理を行います。
1. 配列の中にある全ての文字列の長さを計算する。
2. 必要なメモリサイズを一度だけ計算して、ドンと確保する。
3. 各文字列をその場所に一気に流し込む。

「何度もメモリを確保し直す」のではなく、「一度だけ、完璧なサイズのメモリを確保する」。この違いが、処理速度に天と地ほどの差を生むのです。

図解イメージ

  • 悪い例 (`&` 結合):

`[A]` $\rightarrow$ `[A][B]` $\rightarrow$ `[A][B][C]` $\rightarrow$ `[A][B][C][D]` …
(毎回、前の分を全部コピーして作り直している)

  • 良い例 (`Join` 活用):

`[A]`, `[B]`, `[C]`, `[D]` (配列という棚に、バラバラに置いておく)
$\downarrow$
`Join` 実行!
$\downarrow$
`[A][B][C][D]` (一発で完成形を作る)

4. 実践的なアドバイス:さらに上を目指すために

このテクニックをマスターしたあなたに、さらに現場で役立つTipsを贈ります。

1. `Option Explicit` を必ず書く
コードの冒頭にこれを書くだけで、変数の宣言漏れによるバグ(意図しない挙動)を劇的に減らせます。プロの嗜みです。
2. `ReDim Preserve` の回数を減らす
上記のコードでは `ReDim Preserve dataArray(count + 1000)` としています。1行増えるたびに `ReDim` するのではなく、ある程度まとめて拡張することで、さらにパフォーマンスが向上します。
3. メモリ消費量に注意
`Join` は高速ですが、巨大な文字列(数GBなど)を作ろうとすると、やはりメモリ不足になります。あまりに巨大なファイルの場合は、一括で読み込まず、行ごとに処理して別のファイルに書き出す「ストリーム処理」を検討してください。

まとめ

  • `&` によるループ内での文字列結合は、メモリ再割り当ての嵐を招き、極めて遅い。
  • 配列にデータを蓄積し、最後に `Join` で一括結合するのがプロの定石。
  • 「メモリをいかに無駄なコピーから守るか」が、高速化の鍵。

「なんとなく動くコード」から「計算機科学を意識した効率的なコード」へ。
このステップをクリアしたあなたは、もう立派な自動化エンジニアです。自信を持って、次のプロジェクトに挑んでくださいね!

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