Excelスケジューリングの暗黒面:なぜあなたのスケジュール計算エンジンは遅いのか
大規模なプロジェクト管理やリソース配分のシミュレーションツールをVBAで構築した際、こんな悪夢に直面したことはないだろうか。
「数万行のタスクに対し、ASAP(可能な限り早く)やALAP(可能な限り遅く)の制約条件や依存関係をループで書き換えた途端、PCのファンが唸りを上げ、処理が完了するまでに数分、いや数十分もかかるようになった」
もし、あなたが制約条件を変更するたびにセルへ値を書き込み、その都度Excelのデフォルト機能に甘えて再計算を放置しているなら、それは「再計算エンジンの暴走」を自ら引き起こしている証拠だ。
今回は、Project VBAの奥底にある計算エンジンのライフサイクルを完全に掌握し、ミリ秒単位でスケジュール計算を爆速化させるプロフェッショナル・アーキテクチャを伝授する。
—
1. なぜ「都度計算」はプロジェクトツールの癌なのか?
Excelはデフォルトで `Application.Calculation = xlCalculationAutomatic`(自動計算)に設定されている。これは一般のユーザーにとっては神の機能だが、大量の制約条件や依存関係(前・後続タスクのリンク)を持つスケジュールエンジンにおいては最悪の呪いと化す。
非効率なアプローチのメカニズム
1. ループの1回目でタスクAの開始日を変更する。
2. Excelは「データが変更された」と検知し、ワークブック全体(あるいは依存する数式ツリー全体)の再計算を強制的に走らせる。
3. ループの2回目でタスクBの制約を変更する。
4. また全数式の再計算が走る。
$N$ 件のタスクに対してこれを行えば、計算量は $O(N)$ どころか、依存関係の連鎖によって $O(N^2)$ に跳ね上がる。これが、数万行のスケジュール変更でツールがフリーズする根本原因である。
模範解答:トランザクション的・非同期制御
データベースのトランザクションと同じだ。大量のデータを書き込んでいる最中はログの書き込み(再計算)を停止し、すべての変更がアトミック(不可分)に完了したその瞬間に、一度だけインデックスの再構築(一括再計算)を命じる。これが、プロのVBAアーキテクトが採るべき「計算エンジンの非同期制御」である。
—
2. 実装アーキテクチャの要件
堅牢なスケジュール計算エンジンを構築するためには、単に `Application.Calculation = xlCalculationManual` にするだけでは不十分だ。以下の3点に配慮した「防御的プログラミング」が求められる。
1. 状態の確実な復元(例外安全性の確保)
エラーやユーザーの割り込み(Ctrl+Break)によってマクロが中断された場合、Excelの計算モードが「手動(Manual)」のまま固定されてしまう事故が後を絶たない。必ず `On Error Goto` によるクリーンアップ処理を実装すること。
2. 画面描画の完全な凍結
計算だけでなく、セルの値変更に伴う画面描画(ScreenUpdating)も同時に抑制することで、CPUリソースを計算処理に100%集中させる。
3. イベントの抑制
ワークシート変更イベント(`Worksheet_Change`)などが多重発火して無限ループや予期せぬパフォーマンス低下を招くのを防ぐため、`EnableEvents` も同時に制御する。
—
3. プロダクションコード:爆速スケジュール計算エンジンコントローラー
以下のコードは、実際の業務システムでそのまま流用できる堅牢なクラス、あるいはモジュールの設計パターンである。依存関係の塊であるASAP/ALAPの制約を一括適用するシーンを想定している。
Option Explicit
‘ ==============================================================================
‘ 模块名: MdlScheduleEngine
‘ テーマ: Application.Calculationプロパティによる再計算エンジンの非同期制御
‘ 概要: 大量制約変更時の再計算・画面描画・イベントを完全にコントロールし、
‘ 処理速度を極限まで高めるためのデザインパターン
‘ ==============================================================================
Public Sub ExecuteBatchScheduleUpdate()
‘ 1. 状態退避変数
Dim origCalculation As Long
Dim origScreenUpdating As Boolean
Dim origEnableEvents As Boolean
‘ 2. エラーハンドリング用のフラグ
Dim isInitialized As Boolean
isInitialized = False
On Error GoTo ErrorHandler
‘ ==========================================================================
‘ 【フェーズ1】環境の凍結と非同期制御への移行
‘ ==========================================================================
With Application
origCalculation = .Calculation
origScreenUpdating = .ScreenUpdating
origEnableEvents = .EnableEvents
‘ 計算エンジンを手動モードへ(自動再計算の完全停止)
.Calculation = xlCalculationManual
‘ 画面描画を停止し、描画コストをゼロにする
.ScreenUpdating = False
‘ イベントハンドラの多重発火を抑制
.EnableEvents = False
End With
isInitialized = True
‘ ==========================================================================
‘ 【フェーズ2】高負荷な一括データ書き込み・制約変更シミュレーション
‘ ==========================================================================
‘ ※ここに大量のASAP/ALAP制約の書き込み処理が入る
Call ProcessHeavyConstraintsSimulation()
‘ ==========================================================================
‘ 【フェーズ3】全処理完了後の「一括再計算」とクリーンアップ
‘ ==========================================================================
‘ 全てのデータ・制約が揃った状態で、初めて再計算を実行する
Application.Calculate
‘ 正常終了時の復元
Call RestoreEnvironment(origCalculation, origScreenUpdating, origEnableEvents)
MsgBox “スケジュール計算エンジンによる一括処理が正常に完了しました。”, vbInformation, “完了”
Exit Sub
ErrorHandler:
‘ 異常終了時でも確実にExcelの環境を復元する(デッドロック防止)
If isInitialized Then
Call RestoreEnvironment(origCalculation, origScreenUpdating, origEnableEvents)
End If
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
End Sub
‘ ——————————————————————————
‘ 内部処理シミュレーション(数万行のタスクを想定したダミー処理)
‘ ——————————————————————————
Private Sub ProcessHeavyConstraintsSimulation()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim i As Long
Dim lastRow As Long
lastRow = 10000 ‘ 例: 1万行のタスク
‘ 配列を用いた高速データ処理(セルへの直接アクセスを最小限にする)
Dim dataArr() As Variant
dataArr = ws.Range(“A1:E” & lastRow).Value
For i = 2 To lastRow
‘ ASAP/ALAPの制約ロジックをここで高速に演算
‘ 例としてダミーの値を格納
dataArr(i, 4) = “ASAP”
dataArr(i, 5) = Date + i
Next i
‘ 一括でワークシートへ書き戻し
ws.Range(“A1:E” & lastRow).Value = dataArr
End Sub
‘ ——————————————————————————
‘ 環境復元プロシージャ
‘ ——————————————————————————
Private Sub RestoreEnvironment(ByVal calc As Long, ByVal upd As Boolean, ByVal evs As Boolean)
With Application
.Calculation = calc
.ScreenUpdating = upd
.EnableEvents = evs
End With
‘ フル計算を強制する場合(必要に応じて)
If calc = xlCalculationAutomatic Then
.CalculateFull
End If
End Sub
—
4. データベース連携・ファイルI/Oにおける致命的な罠
この非同期制御アーキテクチャを導入する際、外部データベース(SQL Server, SQLiteなど)や外部ファイル(CSV/JSON)と連携する場合は、さらに一歩踏込んだ設計が必要になる。
1. 「部分計算」の誘惑に負けるな
`Application.Calculate` の代わりに `Worksheet.Calculate` や `Range.Calculate` を使いたくなる場面がある(特定のシートだけ再計算させたい場合など)。しかし、スケジュールエンジンの場合、タスクの依存関係はシート間、あるいはワークブック全体に跨ることが多い。
「ケチらずに `Application.Calculate` で全体を一度に評価させる」方が、結果的に数式エンジンの依存関係ツリーの整合性が保たれ、バグの温床を防げる。
2. トランザクション境界の同期
データベースから最新のWBS(Work Breakdown Structure)やリソース制約を読み込み、Excel上で計算を行った後、再びデータベースに書き戻すフローを考えてほしい。
- 誤った設計: DB読み込み ➔ 1行ごとにExcel書き込み(都度計算) ➔ DB書き戻し
- プロの設計: DB一括読み込み ➔ 画面・計算・イベントを凍結 ➔ 配列で一括処理 ➔ `Application.Calculate` による一括再計算 ➔ DB一括書き戻し(バルクインサート/アップデート)
このパイプラインを守ることで、I/Oのボトルネックと計算エンジンのボトルネックを完全に分離し、エンタープライズグレードのパフォーマンスを実現できる。
—
5. チーフアーキテクトからの提言
VBAは「おもちゃの言語」ではない。適切なアーキテクチャとオブジェクト・エンジンのライフサイクルに対する深い理解があれば、C#やPythonで書かれたデスクトップアプリに匹敵する高速な計算エンジンへと昇華させることができる。
今回紹介した `Application.Calculation` の非同期制御は、単なるテクニックではなく、「システムに負荷をかける期間と、評価を行う期間を明確に分離する」というエンジニアリングの基本原則そのものだ。
あなたの作るスケジュールツールから「カクつき」と「フリーズ」を駆逐し、ユーザーに圧倒的なストレスフリーの体験を提供してほしい。
