【テクニカル・上級編】【上級者向け】大規模プロジェクトの「ベースライン比較」を高速化する、バイナリ比較アルゴリズムの適用 – Project VBA解析バイブル

スポンサーリンク

【極限の最適化】Project VBAで数万行のベースライン差分を秒速で叩き出す技術

大規模プロジェクトにおいて、MS Projectの標準機能である「ベースライン比較」のレポート機能に頼り切っていないだろうか。数万行規模のWBSにおいて、標準のレポート出力は冗長なオーバーヘッドを抱えており、実用的な解析には耐え難い。

真のエンジニアは、オブジェクトモデルの海を泳ぐのではなく、バイナリに近いレイヤーでメモリを制御する。今回は、Project VBAの限界を突破し、メモリを極限まで節約しながらベースラインの差分を高速特定するアーキテクチャを伝授する。

1. 悲劇の根源:Project Object Modelの罠

`Task` オブジェクトをループで回し、`BaselineStart` や `BaselineFinish` を直接参照するコードは、大規模プロジェクトでは「メモリリークの温床」であり「パフォーマンスの墓場」だ。

Project VBAの `Task` オブジェクトは、アクセスするたびに内部で複雑なプロパティ解決が行われる。数万回この呼び出しを繰り返せば、VBAのガベージコレクションは追いつかず、メモリ消費量は右肩上がりに増大する。

極意: オブジェクトへのアクセスは最小限に留め、一度の走査でデータをフラットなメモリ構造へ転送せよ。

2. アーキテクチャ:高速差分抽出のアルゴリズム

差分抽出を高速化するための戦略は以下の3段階に集約される。

1. 高速バッファリング: `Task.UniqueID` をキーとした `Scripting.Dictionary` を作成し、ベースラインデータを数値配列(Variant)に一括格納する。
2. バイナリ的比較: 浮動小数点数(Date型)の直接比較による高速化。
3. メモリ解放: `Set Object = Nothing` をループの適切な箇所で徹底し、スコープを強制的に閉じる。

実装コード:差分抽出エンジン

以下のコードは、プロパティアクセスを最小化し、メモリ負荷を極限まで低減させた実装サンプルである。

‘ メモリ使用量を抑えつつ、高速にベースライン差分を抽出する
Sub ExtractBaselineDiffFast()
Dim proj As Project
Dim tsk As Task
Dim dictBase As Object
Set dictBase = CreateObject(“Scripting.Dictionary”)

Set proj = ActiveProject

‘ 1. メモリを確保し、ベースラインデータを一括マッピング
‘ オブジェクトのプロパティアクセス回数を最小化する
For Each tsk In proj.Tasks
If Not tsk Is Nothing Then
‘ IDをキーに、開始日と終了日を配列で格納
dictBase.Add tsk.UniqueID, Array(tsk.BaselineStart, tsk.BaselineFinish)
End If
Next tsk

‘ 2. 比較ロジック(ここでは簡略化して出力のみ)
Dim diffCount As Long
For Each tsk In proj.Tasks
If Not tsk Is Nothing Then
Dim baseData As Variant
baseData = dictBase(tsk.UniqueID)

‘ Date型は内部的にDoubleとして比較可能
‘ 閾値判定を行うことで、微細な誤差(0.00001等)を無視した比較が可能
If Abs(CDbl(tsk.Start) – CDbl(baseData(0))) > 0.00001 Then
Debug.Print “ID:” & tsk.UniqueID & ” がベースラインから乖離しています。”
diffCount = diffCount + 1
End If
End If
‘ オブジェクトの明示的解放(大規模ループでは重要)
Set tsk = Nothing
Next tsk

‘ 3. 後処理
Set dictBase = Nothing
MsgBox “差分件数: ” & diffCount, vbInformation
End Sub

3. レガシー環境におけるメモリ最適化の極意

数万タスクを扱う場合、VBAのグローバルなメモリ空間は非常に脆弱だ。以下の手法を徹底することで、システムクラッシュを防ぐことができる。

  • Variant型の活用: `Task` オブジェクトを直接保持するのではなく、`UniqueID` と `Double` に変換した値のみを配列に保持せよ。これにより、オブジェクトの参照カウンタ負荷を大幅に削減できる。
  • Windows APIの活用: 大規模なデータセットを扱う場合、`GlobalAlloc` や `LocalAlloc` を駆使したメモリ管理が必要になる局面もあるが、まずは上記のように「Dictionaryへの値コピー」を徹底するだけで、処理時間は数分の一に短縮される。
  • DoEventsの適切な配置: 数万行のループ中、UIがフリーズしないよう `DoEvents` を挿入するが、これは処理速度を低下させる。1,000回ループごとに一度呼び出す等の工夫が不可欠だ。

4. チーフアーキテクトからの提言

システムは「動けば良い」のではない。「期待されるリソース内で、いかに美しく計算を終えるか」が全てだ。

MS Projectの標準レポートでさえ数分かかる処理を、わずか数秒で完遂させるこの手法は、単なるコードテクニックではない。Projectという巨大なデータ構造を、メモリという「戦場」でどう制御するかというアーキテクチャの精神そのものだ。

もし、貴殿がさらに高みを目指すのであれば、VBAの枠を超え、`Microsoft.Office.Interop.MSProject` を用いたC#でのDLL開発を検討すべきだ。マネージドコードによるメモリ管理とマルチスレッド処理は、VBAでは到達できない領域を切り拓く。

技術の深淵へようこそ。また現場で会おう。

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