Visio PDF出力の「見えない壁」を突破する:ページ単位プロファイラーの実装
大規模なVSDXファイルをPDF化する際、進行バーが特定の地点で数分間停止した経験はないだろうか。VisioのPDF変換エンジンはブラックボックスであり、レンダリング負荷は「描画の複雑さ」と「シェイプの履歴(ヒストリー)」に依存する。
本稿では、どのページがPDF変換のボトルネックとなっているかを物理的に特定し、Visioの描画エンジンを極限まで制御するためのプロファイリング手法を伝授する。
—
1. なぜPDF出力は「重い」のか
VisioのPDF変換プロセスは、単なる画像出力ではない。各ページに対し、以下のプロセスが順次実行される。
1. メタデータとレイヤーの解析: グループ化されたシェイプのネスト構造のフラット化。
2. ベクトル描画の変換: EMF/WMFからPDFプリミティブへの再計算。
3. フォント埋め込みとラスタライズ: 高解像度イメージが含まれる場合のメモリバッファ確保。
特に、数千のシェイプを持つページや、古い図面からインポートされた「ゴミシェイプ(使用されないスタイルや影付きオブジェクト)」が混入している場合、描画パイプラインは致命的な停滞を引き起こす。
—
2. 実装:高精度ページプロファイラー
標準の`ExportAsFixedFormat`メソッドは、実行結果を返すが、ページごとの経過時間は教えてくれない。我々は、「ページ単位の個別出力」と「Windows APIによる高精度タイマー」を組み合わせる必要がある。
実装コード
Option Explicit
‘ 高精度タイマー用のWindows API
If VBA7 Then
Private Declare PtrSafe Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
Else
Private Declare Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
End If
Public Sub ProfilePdfExport(ByVal targetDoc As Visio.Document, ByVal outputPath As String)
Dim pag As Visio.Page
Dim freq As Currency, startCount As Currency, endCount As Currency
Dim elapsed As Double
QueryPerformanceFrequency freq
Debug.Print “— プロファイリング開始: ” & Now & ” —”
For Each pag In targetDoc.Pages
‘ ページ単位で計測開始
QueryPerformanceCounter startCount
‘ 1ページのみをPDFとして出力(暫定ファイルとして生成)
‘ 本来はPage.ExportAsFixedFormatだが、単一ページ出力には工夫が必要
‘ ここでは、ページ単位の変換コストを計測するためにダミーPDFを作成
On Error Resume Next
pag.ExportAsFixedFormat visFixedFormatPDF, outputPath & “_” & pag.Index & “.pdf”
On Error GoTo 0
QueryPerformanceCounter endCount
‘ 経過時間を算出 (ミリ秒)
elapsed = (endCount – startCount) / freq 1000
Debug.Print “Page: ” & pag.Name & ” | Time: ” & Format(elapsed, “0.00”) & ” ms”
‘ オブジェクト解放の徹底(メモリリーク防止)
Set pag = Nothing
Next
Debug.Print “— プロファイリング終了 —”
End Sub
—
3. シニアエンジニアが意識すべき「メモリとオブジェクトのライフサイクル」
上記のコードを単に動かすだけでは不十分だ。大規模な図面を扱う際、以下の原則を守らなければVBAのメモリ管理はすぐに破綻する。
- 明示的なメモリ解放の徹底: `Set pag = Nothing`は必須だ。VisioのオブジェクトモデルはCOMベースであり、参照カウンタがゼロにならない限り、大規模なドキュメントのメモリは解放されない。特にループ処理内でのオブジェクト生成は、GC(ガベージコレクション)を待たずに破棄する癖をつけろ。
- イベントの抑制: 大量出力時は `Application.ScreenUpdating = False` を徹底し、描画負荷を最小化せよ。
- ファイルI/Oのボトルネック: 上記コードではページごとにファイルを作成しているが、これはディスクI/Oを発生させる。本番環境でこれを避ける場合は、PDFライブラリ(Adobe Acrobat SDKやサードパーティ製API)を介したメモリ内ストリーム処理への移行を検討すべきだ。
—
4. ボトルネックが見つかった後の最適化策
特定された「遅いページ」に対して、以下の手順でメスを入れるのが定石だ。
1. グループの階層を解体せよ: 5階層以上のグループ化は、PDFレンダラーにとって地獄だ。`Ungroup`を繰り返し、単純な構造に戻す。
2. メタファイルのクリーンアップ: `Shape.Paths`が異常に多いシェイプは、以前のCADインポート等のゴミを抱えている可能性がある。図面を一度SVGでエクスポートし、再度インポートすることで構造をクリーン化できる場合がある。
3. フォントの統一: ページ内に多種類のフォントが混在すると、PDF変換時のフォント埋め込みコストが指数関数的に増大する。
—
結びに:自動化の真髄
自動化とは、単に人間がやる作業をプログラムに置き換えることではない。「システムが何を処理し、どこで詰まっているのか」というブラックボックスを、コードというメスで可視化することこそが、伝説的なエンジニアの仕事だ。
このプロファイラーが、あなたの現場の「終わらない変換処理」を終わらせる一助となれば幸いである。コードの細部に宿る執念こそが、大規模システムを安定稼働させる唯一の鍵だ。
