【テクニカル・上級編】大規模プロジェクトにおけるリソース情報の読み込み高速化テクニック – Project VBA解析バイブル

スポンサーリンク

【Project VBA極限最適化】数千件のリソースを秒速で処理せよ:オブジェクトアクセスを断つ「配列×メモリ」の全技術

Microsoft Project(Project VBA)の現場において、「数千件規模のリソース登録・コスト割り当て」を愚直なオブジェクト操作で実装した開発者は、遅かれ早かれ絶望を味わうことになる。

`ActiveProject.Resources` コレクションに対し、ループ内で `Add` やプロパティの読み書きを繰り返すコード。――それはVBAランタイムとCOMコンポーネントの間で不毛なコンテキストスイッチを毎秒何千回も発生させ、進捗バーが微動だにしない「沈黙の時間」を生成するだけの悪行に等しい。

真のシニアアーキテクトであれば知っているはずだ。「遅いのはVBAの計算能力ではなく、COMオブジェクトへの過剰なアクセスである」という冷徹な事実を。

本稿では、Project VBAにおけるリソース情報の読み込み・書き込みを極限まで高速化し、数千件のデータ処理を「秒単位」で完結させるためのアーキテクチャと実装コードを提示する。

—

1. なぜオブジェクト経由の操作は遅いのか?(COMの呪縛)

Projectのオブジェクトモデルは、背後でCOM(Component Object Model)を介してネイティブのC++エンジンと通信している。

VBAのコードから `Resource.StandardRate` や `Resource.CostPerUse` といったプロパティにアクセスするたびに、以下のコストが発生する。
1. マーシャリング(Marshaling)の発生: VBAのランタイム環境からCOM境界を越えてネイティブ環境へコンテキストが移行する。
2. 参照カウントの増減: 一時的なCOMオブジェクトの生成と破棄が水面下で行われる。
3. Undoスタックの肥大化: プロパティ変更のたびにトランザクションログが積まれ、メモリを圧迫する。

これを数千件のリソースに対してループで行えば、パフォーマンスが崩壊するのは必然である。

解決のアプローチ

オブジェクトへのアクセス回数を物理的に「最小限(理想はゼロ)」に抑え、データをメモリ上の二次元配列(Variant)に一度にロードして処理する。これが大規模プロジェクトを制する唯一の解法である。

—

2. アーキテクチャ設計:一括バッファリングと配列処理

大規模リソースの高速処理を実現するため、以下の3ステップでアーキテクチャを構築する。

1. 一括読み込み(Bulk Read): リソースのプロパティを一度だけスキャンし、Variant配列へダイレクトに格納。
2. メモリ上での高速演算: VBAの配列内でフィルタリングやコスト計算を完結させる。
3. 一括書き込み(Bulk Write): 変更を加えた配列を、最小限のオブジェクト操作でProjectへ流し込む(または外部システムへ高速出力)。

—

3. 実装コード:秒速リソースローダー & コスト最適化エンジン

以下のコードは、数千件のリソース情報を配列にバッファリングし、超高速で処理するための実践的なモジュールである。

Option Explicit

‘ Windows API: 大規模配列のメモリコピーや高速化に必要な場合のエントリ(必要に応じて拡張)
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If

Public Sub HighSpeedResourceProcessor()
Dim startTime As Double
startTime = Timer

Dim proj As Project
Set proj = ActiveProject

Dim resCount As Long
resCount = proj.Resources.Count

If resCount = 0 Then
MsgBox “処理対象のリソースが存在しません。”, vbExclamation
Exit Sub
End If

‘ —————————————————-
‘ 1. 【一括読み込み】COMアクセスを排除し、配列へ全データを転送
‘ —————————————————-
‘ 構造: (1 to 項目数, 1 to リソース数) の二次元配列を確保
‘ 項目: ID, Name, StandardRate, OvertimeRate, CostPerUse, MaxUnits
Dim rawData() As Variant
ReDim rawData(1 To 6, 1 To resCount)

Dim r As Resource
Dim i As Long

i = 0
‘ 画面描画とイベントを停止し、COMの描画スレッド負荷をゼロにする
Application.ScreenUpdating = False

For Each r In proj.Resources
If Not r Is Nothing Then
i = i + 1
rawData(1, i) = r.ID
rawData(2, i) = r.Name
rawData(3, i) = r.StandardRate
rawData(4, i) = r.OvertimeRate
rawData(5, i) = r.CostPerUse
rawData(6, i) = r.MaxUnits
End If
Next r

‘ —————————————————-
‘ 2. 【メモリ上での高速演算】配列内だけでコスト調整ロジックを実行
‘ —————————————————-
Dim j As Long
Dim currentRate As Double

For j = 1 To resCount
‘ 例: 標準単価が一定値未満のリソースに対し、一律でインフレ調整係数(例: 1.05)を適用
If rawData(3, j) > 0 Then
currentRate = CDbl(rawData(3, j))
‘ 演算処理のシミュレーション
rawData(3, j) = currentRate 1.05
End If
Next j

‘ —————————————————-
‘ 3. 【一括書き込み/出力】データベース連携やログ出力、あるいはProjectへの反映
‘ —————————————————-
‘ ここでは外部CSVへの超高速エクスポート、またはADO/Recordset経由でのDB一括登録の基盤となる
Call ExportToCSV_MemoryStream(rawData, “C:\Temp\Resource_Optimized.csv”)

‘ 画面描画の復元
Application.ScreenUpdating = True

MsgBox “処理完了: ” & resCount & “件のリソースを ” & Format(Timer – startTime, “0.00秒”) & ” で処理しました。”, vbInformation
End Sub

Private Sub ExportToCSV_MemoryStream(ByRef data() As Variant, ByVal filePath As String)
‘ ADODB.Stream を用いた、I/O効率を極限まで高めたテキスト出力
Dim stream As Object
Set stream = CreateObject(“ADODB.Stream”)

With stream
.Type = 2 ‘ adTypeText
.Charset = “UTF-8”
.Open

Dim rows As Long, cols As Long
Dim r As Long, c As Long
Dim lineBuffer As String

cols = UBound(data, 1)
rows = UBound(data, 2)

‘ ヘッダー書き込み
.WriteText “ID,Name,StandardRate,OvertimeRate,CostPerUse,MaxUnits” & vbCrLf

‘ チャンク書き込みのシミュレーション(数千行をメモリ上で連結して一気に出力)
For r = 1 To rows
lineBuffer = “”
For c = 1 To cols
lineBuffer = lineBuffer & CStr(data(c, r)) & IIf(c < cols, ",", "") Next c .WriteText lineBuffer & vbCrLf Next r .SaveToFile filePath, 2 ' adSaveCreateOverWrite .Close End With ' オブジェクトの明示的解放(メモリリークの完全防止) Set stream = Nothing End Sub ---

4. シニアエンジニアが押さえるべき「メモリ管理とリスクヘッジ」

オブジェクトの明示的解放(`Set … = Nothing`)の哲学

VBAのガベージコレクタは、スコープを抜けた瞬間に動くとは限らない。特に `For Each r In proj.Resources` のようなイテレーションでは、内部で生成されたCOMラッパーオブジェクトがループの最後までメモリに残留し続けるケースがある。
数万件規模の処理において、不要になったオブジェクト参照は即座に `Set r = Nothing` で切断し、VBAランタイムにヒープ領域を返還させることがクラッシュを防ぐ鉄則だ。

画面描画とイベントの完全遮断

大規模処理の前には必ず以下を記述せよ。

Application.ScreenUpdating = False
Application.Calculation = pjCalculationManual ‘ 必要に応じて自動計算も停止

処理終了時には `True` に戻すことを忘れてはならない。これを怠ると、ProjectのGUIが1件のプロパティ変更ごとにガントチャートを再描画しようとし、CPU使用率が100%に張り付いたままフリーズする。

—

5. システム間連携への応用:外部DB/APIとの非同期ブリッジ

この配列処理モデルの真価は、Microsoft Projectを単なる「ローカルのスケジュール管理ツール」ではなく、「企業リソース管理(ERP)システムの中継ノード」として扱うときの発揮される。

`ActiveProject.Resources` から一括抽出したVariant配列を、そのまま `ADODB.Recordset` や `JSON` シリアライザへ流し込むことで、基幹システム(SAPやDynamicsなど)との間で数千件に及ぶリソース・コストデータを数秒で同期することが可能になる。

レガシーなVBA環境であっても、アーキテクチャさえ正しく設計されていれば、モダンなエンタープライズシステムに匹敵するスループットを叩き出すことができるのだ。

妥協のないコードとメモリ管理の哲学を持ち、プロジェクトの遅延という名の「無駄な待ち時間」をエンジニアの技術力で駆逐せよ。

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