パフォーマンス
PDF Oxide v0.3.11 は、3,830 件の PDF コーパスにおいて Python でのテキスト抽出の平均 0.8ms を達成しています。PyMuPDF の 5 倍、pypdf の 15 倍高速で、有効な PDF に対するパス率は 100% です。
ベンチマーク結果
コーパス: 3,830 件の PDF
3 つの独立した公開テストスイートを統合しています:
| スイート | PDF 数 | ソース |
|---|---|---|
| veraPDF | 2,907 | PDF/A 適合性テストコーパス |
| Mozilla pdf.js | 897 | ブラウザ PDF レンダリングテストスイート |
| SafeDocs | 26 | DARPA SafeDocs 不正 PDF コーパス |
このコーパスが信頼できる理由
これらは手作業で選んだ PDF ではありません。各スイートは、標準化団体や主要なオープンソースプロジェクトによって維持されている、確立されたピアレビュー済みのテストコーパスです:
-
veraPDF は PDF 標準コミュニティで 10 年以上使われている公式の PDF/A 適合性バリデータです。2,907 件のテストファイルはアトミックで、各ファイルが PDF 仕様の 1 つの機能をテストします。すべての PDF/A バージョン(1A/1B, 2A/2B/2U, 3A/3B/3U, 4/4E/4F)、PDF/UA アクセシビリティ(UA1, UA2)、ISO 32000-1(PDF 1.7)および ISO 32000-2(PDF 2.0)をカバーしています。CC BY 4.0 ライセンスです。
-
Mozilla pdf.js は Firefox の PDF レンダリングエンジンで、年間数十億の PDF を処理しています。897 件のテストファイルは、実際のレンダリングのエッジケースをカバーしています: 複雑な複数ページのドキュメント、注釈タイプ(ボーダースタイル、ハイライト、キャレット、ファイル添付)、フォームウィジェット、特殊なフォントエンコーディング、大規模なストレステストドキュメント、コンテンツストリームのエッジケースなどです。7 ファイルは正しいエラー拒否をテストするために意図的に破損させたものです。
-
DARPA SafeDocs は、パーサーの堅牢性に焦点を当てた米国政府資金の安全保障研究プログラムです。26 ファイルは最も難しいエッジケースを対象としています: Type3 フォントのコンテンツストリームサイクル、二重 startxref トレーラー、圧縮 PDF 構文、方言のバリエーション、インライン画像のエッジケース、再帰的フォントネスティング、Unicode パスワード(UTF-16 LE を含む)で暗号化された PDF などです。これらのファイルは、脆弱なパーサーをクラッシュ、ハング、またはエクスプロイトするように設計されています。
このコーパスがカバーする範囲
- すべての主要 PDF バージョン: PDF 1.0 から PDF 2.0 まで
- 暗号化とパスワード: AES-256, RC4, Unicode パスワード, UTF-16 LE エンコーディング
- セキュリティエッジケース: 再帰的構造、コンテンツストリームサイクル、不正トレーラー、ファジング生成の破損
- フォントの多様性: TrueType, CIDFont, Type1, Type3, CJK エンコーディング、埋め込みサブセット
- ドキュメントの複雑さ: 単一ページのフィクスチャから 10,000 ページ以上のドキュメント、インライン画像、ネストされた Form XObject
- 正しい拒否: 意図的に壊された 7 ファイル(PDF ヘッダーの欠落、無効な xref ストリーム) — これらを「パース」するライブラリは、信頼性が高いのではなく、セキュリティが低いのです
3,830 ファイルのうち 3,823 は有効な PDF です。7 つの無効なファイルはエラーハンドリングのテストフィクスチャであり、PDF Oxide は 7 つすべてを正しく拒否します。
Python ライブラリ比較
3,830 件の PDF コーパス全体における、PDF あたりのテキスト抽出の平均時間:
| ライブラリ | 平均 | p99 | パス率 | ライセンス |
|---|---|---|---|---|
| PDF Oxide | 0.8ms | 9ms | 100% | MIT |
| PyMuPDF | 4.6ms | 28ms | 99.3% | AGPL-3.0 |
| pypdfium2 | 4.1ms | 42ms | 99.2% | Apache-2.0 |
| pymupdf4llm | 55.5ms | 280ms | 99.1% | AGPL-3.0 |
| pdftext | 7.3ms | 82ms | 99.0% | GPL-3.0 |
| pdfminer | 16.8ms | 124ms | 98.8% | MIT |
| pdfplumber | 23.2ms | 189ms | 98.8% | MIT |
| markitdown | 108.8ms | 378ms | 98.6% | MIT |
| pypdf | 12.1ms | 97ms | 98.4% | BSD-3 |
PDF Oxide は利用可能な Python PDF ライブラリの中で最速です。PyMuPDF とは異なり MIT ライセンスを採用しており、商用利用に AGPL の制約がありません。
Rust ライブラリ比較
| ライブラリ | 平均 | p99 | パス率 | テキスト抽出 |
|---|---|---|---|---|
| PDF Oxide | 0.8ms | 9ms | 100% | 内蔵、プロダクション品質 |
| oxidize_pdf | 13.5ms | 11ms | 99.1% | 基本的 |
| unpdf | 2.8ms | 10ms | 95.1% | 基本的 |
| pdf_extract | 4.08ms | 37ms | 91.5% | 基本的 |
| lopdf | 0.3ms | 2ms | 80.2% | テキスト抽出なし |
lopdf はパースできる PDF に対しては高速ですが、コーパスの 20% で失敗します。pdf_oxide は 100% の信頼性と内蔵テキスト抽出を兼ね備えた唯一の Rust crate です。lopdf にはテキスト抽出機能がなく、フォントデコーディングやスペーシング解析は自分で実装する必要があります。
テキスト品質
PDF Oxide はコーパス全体で PyMuPDF および pypdfium2 と比較して 99.5% のテキストパリティを達成しています。品質は、3,823 件の有効な PDF すべてで抽出テキスト出力を文字単位で比較して測定しました。
コーパス別の内訳
| コーパス | PDF 数 | PDF Oxide 平均 | pypdfium2 平均 | PyMuPDF 平均 |
|---|---|---|---|---|
| veraPDF | 2,907 | 0.7ms | 3.6ms | 4.1ms |
| Mozilla pdf.js | 897 | 1.1ms | 5.8ms | 6.2ms |
| SafeDocs | 26 | 0.9ms | 4.0ms | 4.3ms |
最適化の歴史: v0.3.5 → v0.3.8
v0.3.8 では、2 つの重大な O(n) ボトルネックを解消し、同一コーパスにおいて Python での平均が 23.3ms から 0.8ms に低下しました。
1. ページツリーの一括キャッシュ
変更前: get_page() はキャッシュされていないページごとにルートからページツリーをたどっていました。全ページを順次抽出すると、1 ページあたり O(n)、合計 O(n^2) になっていました。
変更後: 最初のページアクセス時にページツリー全体を一度だけ走査し、すべてのページを HashMap<usize, Object> にキャッシュします。以降のアクセスはすべて O(1) です。
この修正により、10,000 ページの PDF が 55 秒から 332 ミリ秒に改善されました。
2. オブジェクトスキャンのオフセットキャッシュ
変更前: xref テーブルにオブジェクトが見つからない場合、scan_for_object() は見つからないオブジェクトごとに PDF ファイル全体を読み込んでいました。xref に登録されていない構造ツリー要素を数百個持つタグ付き PDF では、数百回のフルファイル読み込みが発生していました。
変更後: ファイルを一度だけスキャンし、すべてのオブジェクトオフセットを HashMap にキャッシュします。以降の参照は O(1) です。
3. シングルパステキスト抽出
変更前: extract_spans() はページコンテンツに対して 2 パス実行していました。最初にドキュメントタイプ(学術論文、新聞、フォームなど)を分類し、次にテキストを抽出するパスです。
変更後: 分類パスを完全に廃止しました。アダプティブなフォント対応しきい値により、シングルパスで同等以上の結果が得られるようになりました。
4. コンテンツストリームの事前アロケーション
変更前: parse_content_stream() はデフォルト容量からオペレーター Vec を構築していたため、大きなコンテンツストリームで再アロケーションが繰り返されていました。
変更後: ストリームサイズに基づいて Vec を事前アロケートしています(data.len() / 20、約 20 バイトあたり 1 オペレーターの見積もり)。
方法論
すべてのベンチマークは同一の方法論で実施しています:
- 各ライブラリが Python multiprocessing を使って 3,830 件すべての PDF を処理(1 プロセスにつき 1 PDF)
- PDF あたり 60 秒のタイムアウト — 超過した場合は失敗としてカウント
- 品質比較のためにライブラリごとに抽出テキストをディスクに保存
- ファイルのオープンから最終テキスト抽出までの実時間を測定
- ウォームアップなし、ファイル間のキャッシュなし
- PDF あたり単一スレッド
ベンチマークハーネスは、18 のライブラリ(Rust 3 個、Python 15 個)すべてを同じマシン、同じコーパス、同じ条件で実行しています。
ベンチマークの再現
公開テストコーパスは自由に利用できます:
- veraPDF: github.com/veraPDF/veraPDF-corpus
- Mozilla pdf.js: github.com/mozilla/pdf.js/tree/master/test/pdfs
- SafeDocs: github.com/pdf-association/safedocs
検証の実行:
cargo run --release --example verify_corpus -- \
/path/to/veraPDF-corpus \
/path/to/pdfjs-test \
/path/to/safedocs \
--csv results.csv
パフォーマンス特性
PDF Oxide が得意な処理
- テキスト抽出: 主要な最適化対象。一般的なドキュメントではサブミリ秒。
- 複数ページの順次抽出: ページツリーキャッシュにより、大きなドキュメントの全ページ抽出が 1 ページ抽出とほぼ同じ速度で処理できます。
- タグ付き PDF: 構造ツリーの走査とオブジェクト解決がキャッシュされています。
- 不正な PDF: フォールバック戦略を備えた寛容なパーシングにより、高コストなリトライを回避します。
線形にスケールする処理
- ページ数: 各ページは独立して処理されます。100 ページは 1 ページの約 100 倍の時間がかかります。
- コンテンツストリームサイズ: オペレーターのパースはストリーム長に対して線形です。
- 画像抽出: 画像の数とサイズに比例します。
遅くなるケース
- OCR 付きスキャン PDF: OCR 処理(有効な場合)はテキスト抽出よりも大幅に遅くなります。
- レンダリング: ページの画像レンダリングはコンテンツの複雑さとターゲット DPI に依存します。
- 高度に暗号化された PDF: AES-256 復号はストリームごとにオーバーヘッドが追加されます。
- 数千のフォントを持つ PDF: フォントパースはドキュメント単位でキャッシュされますが、初回パースはフォント数に応じてスケールします。
次のステップ
- 変更履歴 – 完全なバージョン履歴
- Python ライブラリ比較 – PyMuPDF, pypdf, pdfplumber, pdfminer との詳細比較
- Rust ライブラリ比較 – lopdf, pdf_extract, pdf-rs との詳細比較
- Rust で始める – インストールと最初のテキスト抽出
- Rust API リファレンス – 完全な API ドキュメント