【Word VBA極道】数千行の表を瞬殺せよ。描画コストを極限まで排除する「メモリ・CPU直結」の最適化術
Word VBAで「数千行の表を生成する」という要件を突きつけられたとき、凡百のエンジニアは`Selection.InsertRows`を繰り返し、画面がガタガタと震える様を眺めながらコーヒーをすする。しかし、我々アーキテクトが目指すのは、一瞬の静寂の後に完成形がそこに現れる「不可視の高速化」である。
本稿では、Wordの描画エンジンを沈黙させ、メモリレイアウトを意識した「表生成の極限」を解説する。
—
1. 描画コストの排除:ScreenUpdatingの罠を越えて
VBAの鉄則として`Application.ScreenUpdating = False`を唱える者は多い。だが、Wordの場合、これだけでは不十分だ。Wordは「ページレイアウトエンジン」が常に裏で動いており、行が増えるたびに再計算(リフロー)が発生する。
真の最適化ポイント:
- ScreenUpdatingの無効化: これは基本。
- Undoスタックの肥大化抑制: `Application.UndoRecord.StartCustomRecord`を活用し、生成プロセスを単一のUndo単位として囲い込む。これにより、Wordが各行の変更を個別に履歴管理するオーバーヘッドを排除できる。
2. オブジェクト参照の最適化:Rangeを制する者がVBAを制す
`Selection`オブジェクトを使ってはならない。これはカーソルを動かすたびに描画イベントを誘発する「低速化の主犯」である。常に`Range`オブジェクトを保持し、メモリ上で構造を操作せよ。
実践コード:高速表生成のテンプレート
Public Sub GenerateLargeTableOptimized(ByRef dataArray As Variant)
Dim doc As Document: Set doc = ActiveDocument
Dim rng As Range: Set rng = doc.Range(0, 0)
Dim tbl As Table
‘ 1. 描画と履歴管理の停止
Application.ScreenUpdating = False
Dim undoRec As UndoRecord: Set undoRec = Application.UndoRecord
undoRec.StartCustomRecord “LargeTableGeneration”
‘ 2. 表の初期化:範囲を確定させてから追加
Set tbl = doc.Tables.Add(Range:=rng, NumRows:=1, NumColumns:=Ubound(dataArray, 2))
‘ 3. 行追加の高速化:Rangeを末尾へ移動させて一括挿入
‘ 行ごとにSelectionを動かすのではなく、RangeのEndを拡張し続ける
Dim i As Long, j As Long
For i = 1 To UBound(dataArray, 1)
tbl.Rows.Add
Next i
‘ 4. データ流し込み(Rangeのインデックス参照)
‘ セルへのアクセスはインデックスで行う。オブジェクト生成コストを最小化
For i = 1 To UBound(dataArray, 1)
For j = 1 To UBound(dataArray, 2)
tbl.Cell(i + 1, j).Range.Text = dataArray(i, j)
Next j
Next i
‘ 5. 後処理:明示的な解放と再描画
undoRec.EndCustomRecord
Application.ScreenUpdating = True
‘ メモリ管理:参照の破棄
Set tbl = Nothing
Set rng = Nothing
End Sub
3. なぜ「行追加」をループさせるのか
`Tables.Add`で最初から全行を指定するよりも、`Rows.Add`をループさせる方が安定する場合がある。特に、セルの結合(`Merge`)が絡む複雑な表の場合、一度に巨大なオブジェクトをメモリ上に展開するとWordのヒープ領域が断片化し、後半の処理が急激に鈍化する。
もし数万行に及ぶデータであれば、「Wordの表機能」を捨て、CSVを生成してタブ区切りで流し込み、最後に`ConvertToTable`メソッドで一括変換するのが、Wordの描画コストを回避する唯一の解となる。
4. プロフェッショナルのためのメモリ解放術
VBAはガベージコレクションが優秀とは言い難い。特にWordのCOMインターフェースは、参照が残っているとプロセスがゾンビ化(バックグラウンドに残り続ける)する。
- 明示的Nothingの強制: ループ内で生成した`Range`や`Cell`オブジェクトは、ループ終了直後に必ず`Set … = Nothing`すること。
- DoEventsの活用: 1,000行単位で`DoEvents`を挟むことで、OS側のメッセージキューを解放し、フリーズを防止する。ただし、やりすぎは速度低下を招くため、カウンターで制御せよ。
結論:システムアーキテクトの視座
大量のデータをWordに流し込む作業は、本質的には「ドキュメント生成」ではなく「データ構造の再構築」である。
WordのUI上で作業するのではなく、メモリ上のデータ構造として表を構築し、最後にWordのレンダリングエンジンへ「一気に」流し込む。この意識の転換こそが、レガシーなVBA環境であっても数万行のデータを数秒で処理するための唯一の道である。
次は、API呼び出しによるメモリの直接制御と、Wordの`XMLデータ構造`を直接叩くことで、さらに一段上の高速化に踏み込む手法について論じるとしよう。
諸君、コードを書け。ただし、マシンを泣かせるコードではなく、マシンが喜ぶコードを。
