⚙️ 【提出用図面データ作成の自動化 #3】40MB級DXFの読込・解析を数分→約8秒に。VBAのI/Oを見直した話

目次
📝
会社名、顧客名、実店舗名、店舗番号、共有フォルダ、実際の図面データは公開せず、内容を一般化しています。

この記事で伝えたいこと


約40MBのDXFをVBAで処理したところ、最初は数分単位で時間がかかりました。

「VBAだから遅い」と決めつけず、読込・文字コード変換・走査・書込へ分解して測ると、ボトルネックはI/O方式にありました。

Binary Get / PutとWin32 APIによるCP932変換へ切り替えた結果、基礎処理は約8.25秒まで短縮できました。

💡
処理を分解して測ったことで、I/Oがボトルネックだと分かりました。高速化の方法を考えたのはその後です。

背景


以前の状態

#1で、図種ごとのDXFを用意する運用から、統合DXF1本を材料にする方式へ変えました。以前はDXFを図種ごとに変換・準備するたび、別の図種を選んでいないか、名前を間違えていないかをかなり意識して確認していました。

次の問題は、その統合DXFから提出対象だけを安全に抜き出せるかです。複数図種のレイヤーやBLOCKが混在するため、最初は不要レイヤーをOFFにすれば十分だと考えていました。

なぜ見直したのか

レイヤーをOFFにしても、不要ENTITY自体はDXF内に残ります。提出先でレイヤーをONにすると不要情報が再表示されるため、非表示ではなくデータ削除が必要でした。

さらに、実データ相当の約39MB DXFでは処理時間も問題になりました。

今回やったこと


1. 不要ENTITYを物理削除する方式へ変更

図種ごとに必要レイヤーを定義し、不要な管理対象レイヤーのENTITYを出力から除外します。

2. BLOCK / INSERT参照を追跡

DXFはtop-levelのENTITIESだけでなく、INSERTから参照されるBLOCK内部にも要素があります。

必要なBLOCKを再帰的に追い、内部ENTITYにもレイヤー選択を適用しました。

3. layer 0を図種別に扱う

展開図ではlayer 0のENTITYやnested INSERTが残るケースがあったため、top-levelとBLOCK内部の両方で削除対象にしました。

4. I/OをBinary Get / Putへ変更
CP932 DXF
↓
Binary Get
↓
Win32でUnicodeへdecode
↓
indexed scan
↓
必要ENTITY選択
↓
CP932へencode
↓
Binary Put

実際に確認したこと


約38.88MBのDXFで処理単位を計測しました。計測条件は、NAS上のDXFを読み込み、処理後のデータをローカルへ保存する実運用に近い経路です。

Binary Get             7.172秒
Win32 CP932 decode     0.062秒
Indexed scan           0.907秒
Win32 CP932 encode     0.094秒
Binary Put             0.015秒
--------------------------------
基礎処理               約8.25秒

この約8.25秒は、NASからのDXF読込・文字コード変換・走査・ローカルへの書込という基礎処理だけの計測値です。そのため Binary Get 7.172秒 にはNASからの読込時間も含まれ、ローカルファイルだけを使った純粋なI/O性能ではありません。OCR、ZIP生成、結果確認などを含む提出準備全体の時間とも分けて扱っています。

一方、以前の方式では原本scan約93秒、TEMP scan約135秒、rewrite約113秒と、数分単位でした。

生成した6種類のDXFについて、不要な管理対象レイヤーENTITYがtop-levelとBLOCK内部で0件になっていることも確認しました。

✅
約40MB級DXFのI/O・解析基礎処理と、不要管理レイヤーENTITYの削除は実データ相当で確認済みです。

難しかったこと・うまくいかなかったこと


ADODB.Streamなら速いと思ったが逆だった

全文をまとめて読めば速くなると考えましたが、約40MBの読込だけで4〜5分かかるケースがありました。

layer 0がBLOCK内部に残った

レイヤーテーブルのON/OFFだけでは消えず、ENTITY自体の削除まで必要でした。

Booleanの値が次ループへ残った
Dim keep As Boolean

をループ内で宣言していたため毎回初期化されると思っていましたが、VBAでは前の反復の値が残りました。

各反復の先頭で keep = False を明示し、さらに不要レイヤーが残っていないことを検証する処理を追加しました。

空BLOCK削除は効果が小さかった

空BLOCKを消せばさらに軽量化できると考えましたが、6ファイル合計でも約20KB程度で、全体に対する効果は約0.04%でした。実装優先度を下げました。

どう判断したか


採用した方法
  • 不要レイヤーを非表示ではなくENTITY削除する
  • INSERT参照を追跡してBLOCK内部も処理する
  • Binary Get / Putを使う
  • CP932変換はWin32 APIへ任せる
  • 出力後に不要ENTITY残存を自動検証する
採用しなかった方法
  • ADODB.Streamによる40MB級全文読込
  • レイヤーOFFだけで提出用とみなす
  • 効果量の小さい空BLOCK削除を優先する
言語を変更する前に、どの処理が遅いかを測り、効果量の大きい箇所だけ変えることを優先しました。

結果


できるようになったこと

約40MB級の統合DXFでも、NASから読み込んでローカルへ書き出す基礎処理を約8.25秒で実行できました。

  • 不要な管理対象レイヤーENTITYを物理削除する
  • BLOCK内部の不要ENTITYも除外する
  • 出力後に不要ENTITYが残っていないか自動確認する
まだ確認・改善が必要なこと
  • より多様なDXF生成元での互換性
  • 未知の補助レイヤーを含む実案件での長期検証
  • 本番運用全体の処理時間

AIを使った範囲


AIに任せたこと
  • DXF構造の整理
  • VBA実装案
  • 性能測定コードの作成
  • 残存ENTITYの原因候補整理
  • 出力構造の検証観点
自分で判断したこと
  • レイヤーOFFでは不十分という受入判断
  • 実DXFを使ったベンチマーク
  • どのレイヤーを各図種に残すか
  • 空BLOCK削除を優先しない判断
  • CADでの見た目確認

今回の学び


  1. 「この言語だから遅い」と決めつけず、処理を分解して測る
    • 今回はVBA自体よりI/O経路が大きなボトルネックでした。
  2. 見た目の非表示と、データの削除は別物
    • CADデータでは提出後に設定を変えられる可能性まで考える必要があります。
  3. バグ修正後は再発を検出するValidationまで入れる
    • keep変数の修正だけでなく、残存ENTITYを自動検出するようにしました。
  4. 最適化は効果量を測ってからやる
    • 空BLOCK削除は実装できても、効果が小さいので見送りました。

DXF処理が現実的な速度になったあと、最後に残ったのは「日常業務で壊さず使えるか」でした。

まとめ


約40MBのDXFが遅かった原因は、VBAそのものというよりI/O経路でした。読込・変換・走査・書込に分けて測ったことで、手を入れる場所が見えました。

結果として、NASからの読込を含む基礎処理は約8.25秒まで短縮できました。空BLOCK削除のように効果が小さい最適化は、数字を見て後回しにしています。