パフォーマンスベンチマーク
TCPDF-Next と、3つの主要な PHP PDF ライブラリ(TCPDF、DomPDF、mPDF)を比較した実運用環境でのパフォーマンスベンチマークです。すべてのテストは、同一ハードウェア上の制御された Docker 環境で実行されました。結果は外れ値のノイズを排除するため、20回の反復の中央値を報告しています。
テスト環境
| パラメータ | 値 |
|---|---|
| CPU | Intel Core i9-13900K (x86-64) |
| RAM | 64 GB DDR5 (Docker は 16 GB に制限) |
| Docker | 4 CPUs, 16 GB RAM, Debian bookworm |
| PHP | 8.5.3 (CLI, OPcache 有効, JIT 有効) |
| TCPDF-Next | 1.7.0 |
| TCPDF | 6.10.1 |
| DomPDF | v3.1.4 |
| mPDF | v8.2.7 |
| Artisan (Chrome) | Headless Chromium (CDP 経由) |
| RoadRunner | spiral/roadrunner-http ^3.6(HTTP スループットテスト) |
| ウォームアップ | 3回(破棄) |
| 計測 | 20回(中央値を報告) |
| タイミング | hrtime(true) ナノ秒精度のウォールクロック |
インタラクティブ比較
PHP 8.5.3 + OPcache + JIT · Docker 4 CPUs / 16 GB · i9-13900K · Median of 20 runs
生成速度
各シナリオは、3回のウォームアップの後に20回実行されました。中央値の生成時間を報告しています。
シンプルドキュメント(1ページ)
組み込みの Helvetica フォントを使用した、見出しと基本的なフォーマットテキストを含む A4 1ページのドキュメントです。画像やテーブルは含みません。
| ライブラリ | 時間 (ms) |
|---|---|
| TCPDF-Next | 0.68 |
| TCPDF | 2.55 |
| DomPDF | 4.16 |
| mPDF | 6.71 |
TCPDF-Next は最もシンプルなシナリオを 1 ms 未満で完了します — TCPDF より 3.8x 高速、DomPDF より 6.1x 高速、mPDF より 9.9x 高速です。
請求書(2ページ)
25行の明細項目、小計、ヘッダー、フッター、ロゴ画像を含む2ページの請求書です。
| ライブラリ | 時間 (ms) |
|---|---|
| TCPDF | 1.96 |
| TCPDF-Next | 2.01 |
| mPDF | 15.86 |
| DomPDF | 17.33 |
請求書シナリオでは、TCPDF-Next と TCPDF はほぼ同等です(~1.0x)。両方とも mPDF(7.9x 低速)および DomPDF(8.6x 低速)を大幅に上回っています。
100ページレポート
見出し、段落、構造化データなど、密度の高い混合コンテンツを含む100ページのドキュメントです。
| ライブラリ | 時間 (ms) |
|---|---|
| TCPDF-Next | 34.29 |
| TCPDF | 105.39 |
| mPDF | 1,106.59* |
| DomPDF | 2,129.12 |
TCPDF-Next は100ページのレポートを 34.29 ms で完了します — TCPDF より 3.1x 高速、mPDF より 32.3x 高速、DomPDF より 62.1x 高速です。
JIT 互換性に関する注意
*mPDF の100ページレポートの結果は、mPDF のコードパスにおける PHP JIT セグメンテーション違反(SIGSEGV、終了コード 139)のため、JIT を無効化(opcache.jit=0)して計測されました。OPcache のバイトコードキャッシングは有効のままです。これは、特定の複雑なループパターンに影響する PHP JIT の既知のバグクラスです。その他すべての mPDF シナリオは JIT 有効で実行されました。
TrueType ドキュメント(1ページ)
DejaVu Sans(約700 KB の TrueType フォント)を使用した A4 1ページのドキュメントです。このシナリオは、実際のフォントファイル解析コストを明らかにします — Helvetica(組み込みの Base14 フォントでファイル I/O がゼロ)とは異なります。
| ライブラリ | 時間 (ms) |
|---|---|
| TCPDF-Next | 4.08 |
| TCPDF | 12.11 |
| mPDF | 16.51 |
| DomPDF | 24.14 |
TCPDF-Next は TrueType フォントの解析と埋め込みを、TCPDF より 3.0x 高速、mPDF より 4.0x 高速、DomPDF より 5.9x 高速で処理します。
相対速度(全シナリオ)
すべての値は TCPDF-Next を基準(1.0x ベースライン)としています。値が小さいほど高速です。
| シナリオ | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| シンプルドキュメント | 1.0x | 3.8x | 6.1x | 9.9x |
| 請求書 | 1.0x | ~1.0x | 8.6x | 7.9x |
| 100ページレポート | 1.0x | 3.1x | 62.1x | 32.3x |
| TrueType ドキュメント | 1.0x | 3.0x | 5.9x | 4.0x |
HTML to PDF
HTML 処理アプローチ
ライブラリによって、HTML から PDF への変換には根本的に異なるアプローチが採用されています。ベンチマーク結果を正しく解釈するために、これらの違いを理解することが不可欠です。
直接変換 (TCPDF-Next, TCPDF) — 組み込みの HTML パーサーが HTML タグをトークン化し、単一のストリーミングパスで PDF 描画コマンド(Cell、MultiCell、Image)に直接マッピングします。このアプローチは非常に高速ですが、基本的な HTML タグとインライン CSS のみをサポートしています — Flexbox、Grid、複雑な CSS セレクターには対応していません。
CSS レイアウトエンジン (DomPDF, mPDF) — これらのライブラリは HTML を主要なインターフェースとして設計されています。DomPDF は完全な DOM ツリーを構築し、CSS カスケード(詳細度、継承)を適用し、ボックスモデルレイアウトを計算してから PDF にレンダリングします。mPDF の WriteHTML() も同様に、独自の CSS レイアウトエンジンを通じて HTML を処理します。両方とも直接変換パーサーよりも多くの CSS 機能(フロート、位置指定要素、スタイル付きテーブル)をサポートしていますが、完全なブラウザレベルの CSS3 には及びません。
フルブラウザレンダリング (Artisan / Chrome) — TCPDF-NextArtisan は Chrome DevTools Protocol (CDP) を介してヘッドレス Chromium にレンダリングを委譲します。これにより、ピクセルパーフェクトな CSS3 サポートが提供されます:Flexbox、Grid、Web Fonts、メディアクエリ、CSS 変数 — Chrome ブラウザが生成するものと同一の出力が得られます。
ベンチマークでは、各ライブラリのネイティブアプローチを比較しています:TCPDF-Next と TCPDF は組み込みの直接変換パーサーを使用し、DomPDF と mPDF は CSS レンダリングエンジン(主要な API)を使用し、Artisan は Chrome を使用します。
結果
| ライブラリ | アプローチ | 時間 (ms) |
|---|---|---|
| TCPDF-Next | 直接変換 | 1.51 |
| TCPDF | 直接変換 | 6.60 |
| DomPDF | CSS レイアウトエンジン | 13.69 |
| mPDF | CSS レイアウトエンジン | 29.63 |
| Artisan (Chrome) | フルブラウザレンダリング | 66.70 |
相対時間(HTML to PDF)
| ライブラリ | 相対値 |
|---|---|
| TCPDF-Next | 1.0x |
| TCPDF | 4.4x |
| DomPDF | 9.0x |
| mPDF | 19.6x |
| Artisan (Chrome) | 44.1x |
TCPDF-Next の直接変換パーサーは 2 ms 未満のパフォーマンスを実現します — TCPDF の正規表現ベースのパーサーより 4.4x 高速、DomPDF の CSS レイアウトエンジンより 9.0x 高速、mPDF より 19.6x 高速です。Artisan (Chrome) は 44.1x 低速ですが、他のどのライブラリにも匹敵できない完全な CSS3 忠実度を提供します。
Artisan Chrome — フェーズ別内訳
Artisan (Chrome) パイプラインを2つのフェーズに分解します:
- Chrome CDP Render — ヘッドレス Chrome が
printToPDFを介して HTML を PDF バイトに変換します - PDF Import + Embed — TCPDF-Next が Chrome の PDF を解析し、ページを Form XObject として抽出し、対象ドキュメントに埋め込みます
| フェーズ | 中央値 (ms) | 平均 (ms) | 最小 (ms) | 最大 (ms) | 標準偏差 |
|---|---|---|---|---|---|
| Chrome CDP Render | 81.17 | 81.17 | 65.51 | 95.80 | 4.84 |
| PDF Import + Embed | 1.96 | 2.08 | 1.60 | 2.87 | 0.40 |
| 合計 | 83.35 | 83.29 | 68.20 | 97.56 | 4.70 |
時間分布: Chrome CDP = 97.4% | PDF Import = 2.3%
Chrome の printToPDF がパイプラインの 97.4% を占めています。PDF Import フェーズ(PdfReader + PageImporter + XObject 埋め込み)の追加は約 2 ms のみで、無視できるオーバーヘッドです。
標準テストとフェーズ別テストの違い
標準の Artisan テスト(66.70 ms)は、BrowserPool のキープアライブを利用した統合 writeHtmlChrome() メソッドを使用しています。フェーズ別テスト(合計 83.35 ms)は各フェーズを個別に計測するため、計測オーバーヘッドが追加されます。両方とも同じキープアライブの Chrome インスタンスを使用しています — 初回の Chromium 起動にかかる約 250 ms のコールドスタートコストは、数千のリクエストにわたって償却される一回限りのコストであるため除外されています。
どのアプローチを使うべきか
シンプルな HTML(テーブル、基本的なフォーマット)の場合は、TCPDF-Next の組み込み HTML パーサーを使用してください(1.51 ms)。ピクセルパーフェクトな忠実度が必要な複雑な CSS3 レイアウト(Flexbox、Grid、Web Fonts)の場合は、Artisan を使用してください — 約 67 ms のオーバーヘッドで Chrome レンダリングエンジンのフルパワーが利用できます。
ワーカーライフサイクル(DocumentFactory vs Standalone)
TCPDF-Next は、長時間稼働する PHP ワーカー(RoadRunner、Swoole、Laravel Octane)向けに設計された DocumentFactory パターンを提供します。ファクトリは起動時に共有レジストリ(FontRegistry、ImageRegistry)を事前初期化しロックします。各 HTTP リクエストはファクトリから軽量で使い捨ての Document を作成し、リクエストごとの初期化オーバーヘッドを排除します。
このセクションでは、DocumentFactory(共有・ロック済みレジストリ)と createStandalone()(呼び出しごとに新しいレジストリ)を比較します。
組み込みフォント(Helvetica)
| モード | 中央値 (ms) | ピークメモリ (MB) | ファイルサイズ (KB) |
|---|---|---|---|
| DocumentFactory | 0.60 | 4.0 | 3.3 |
| createStandalone() | 0.70 | 4.0 | 3.3 |
結果: ほぼ同等(比率 0.86x)。組み込みフォント(Helvetica)の場合、キャッシュするフォントファイル解析がないため、両モードのパフォーマンスは同一です。DocumentFactory の真の優位性は TrueType フォントで現れます。
TrueType フォント(DejaVu Sans)
これは DocumentFactory の価値を示す重要なテストです。上記の Helvetica テスト(組み込みフォント、解析ゼロ)とは異なり、このテストでは DejaVu Sans(約700 KB の TrueType フォント)を使用します。DocumentFactory は起動時に解析済みフォントデータを事前登録してキャッシュし、後続のリクエストではすべてのフォントファイル I/O をスキップします。createStandalone() はリクエストごとに .ttf ファイルを解析する必要があります。
| モード | 中央値 (ms) | ピークメモリ (MB) | ファイルサイズ (KB) |
|---|---|---|---|
| Factory (TTF cached) | 2.60 | 6.0 | 24.5 |
| Standalone (TTF parse) | 4.09 | 6.0 | 24.3 |
Factory の高速化: 1.6x — キャッシュされたフォント解析により、リクエストあたり約 1.5 ms が削減されます。RoadRunner/Swoole ワーカーが毎分 1,000 リクエストを処理する場合、毎分約 25 秒の CPU 時間を節約します。
ピークメモリ使用量
すべての値は MB(中央値)です。各ライブラリのベンチマークは独自の PHP サブプロセスで実行されます — 必要なオートローダーのみがロードされるため、memory_get_peak_usage() はそのライブラリ単体の実際のメモリコストを反映しています。
標準シナリオ
| シナリオ | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| シンプルドキュメント | 4.0 | 12.0 | 6.0 | 14.0 |
| 請求書 | 4.0 | 12.0 | 12.0 | 14.0 |
| 100ページレポート | 4.0 | 12.0 | 66.0 | 27.9* |
| TrueType ドキュメント | 6.0 | 14.0 | 20.0 | 16.0 |
TCPDF-Next は、1ページから100ページのドキュメントまで一貫して 4 MB のフットプリントを維持しており、コンパクトなページオブジェクトと共有リソース参照による効率的なメモリ管理を実証しています。
HTML to PDF メモリ
| ライブラリ | ピークメモリ (MB) |
|---|---|
| TCPDF-Next | 4.0 |
| Artisan (Chrome) | 4.0 |
| DomPDF | 10.0 |
| TCPDF | 12.0 |
| mPDF | 18.0 |
Artisan (Chrome) は PHP 側のメモリのみを計測しています — ヘッドレス Chrome プロセスは OS によって管理される独自のメモリ空間を持っています。
出力ファイルサイズ
すべての値は KB(中央値)です。
標準シナリオ
| シナリオ | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| シンプルドキュメント | 3.3 | 7.1 | 1.7 | 28.0 |
| 請求書 | 5.0 | 9.2 | 4.0 | 30.2 |
| 100ページレポート | 96.4 | 100.8 | 128.7 | 181.1* |
| TrueType ドキュメント | 24.7 | 101.3 | 16.1 | 42.4 |
DomPDF は、積極的なコンテンツストリーム最適化により、シンプルなドキュメントで最小のファイル(1.7 KB)を生成します。TCPDF-Next は、PDF 2.0 のクロスリファレンスストリームとオブジェクトストリームにより、コンパクトな出力を生成します。TCPDF は大幅に大きい TrueType フォントサブセットを埋め込みます(101.3 KB vs 24.7 KB)。
HTML to PDF ファイルサイズ
| ライブラリ | ファイルサイズ (KB) |
|---|---|
| DomPDF | 5.3 |
| TCPDF-Next | 6.6 |
| TCPDF | 12.6 |
| Artisan (Chrome) | 36.9 |
| mPDF | 46.0 |
Artisan (Chrome) の出力が大きい(36.9 KB)のは、Chrome の printToPDF が埋め込みリソースを含む完全なスタンドアロン PDF を生成するためです。
スループット
スループットテストは、シンプルドキュメントシナリオを使用して30秒間連続で実行されます。値は負荷時の持続的な生成能力を反映しています。
標準 (docs/sec)
| モード | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| シングルスレッド | 2,605 | 1,169 | 233 | 130 |
| 4 ワーカー | 9,221 | 4,163 | 841 | 487 |
1分あたりのドキュメント数
| モード | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| シングルスレッド | 156,284 | 70,134 | 13,956 | 7,800 |
| 4 ワーカー | 553,280 | 249,752 | 50,484 | 29,194 |
4ワーカー構成で、TCPDF-Next は毎秒 9,200 ドキュメント以上 — 毎分 553,000 ドキュメント以上を持続的に処理します。これは TCPDF の 2.2x、DomPDF の 11.0x、mPDF の 19.0x のスループットです。
ワーカーライフサイクル スループット
組み込み Helvetica フォントを使用した DocumentFactory と createStandalone() のスループット比較です。
| モード | DocumentFactory | createStandalone() |
|---|---|---|
| シングルスレッド | 2,490 | 2,515 |
| 4 ワーカー | 9,074 | 9,191 |
組み込みフォントの場合、DocumentFactory と createStandalone() は同等のスループットを生成します — キャッシュするフォント解析がないためです。
TrueType ドキュメント スループット
TrueType フォント(DejaVu Sans)によるスループット — ライブラリごとの実際のフォント解析オーバーヘッドを明らかにします。
| ライブラリ | シングルスレッド (docs/sec) |
|---|---|
| TCPDF-Next | 242 |
| TCPDF | 81 |
| mPDF | 50 |
| DomPDF | 30 |
TCPDF-Next の TrueType スループットは、TCPDF の 3.0x、mPDF の 4.8x、DomPDF の 8.0x です — 効率的なフォントサブセッティングとキャッシングを反映しています。
ワーカーライフサイクル TTF スループット
DocumentFactory(キャッシュされた TrueType フォントデータ)と createStandalone()(リクエストごとに TTF を解析)の比較です。
| モード | Factory (TTF cached) | Standalone (TTF parse) |
|---|---|---|
| シングルスレッド | 364 | 243 |
| 4 ワーカー | 1,327 | 871 |
Factory のスループット優位性: 1.5x(シングルスレッド)。キャッシュされた TrueType フォントデータにより、リクエストごとの .ttf 解析オーバーヘッドが排除されます。4ワーカー構成で、Factory は 1,327 docs/sec を達成し、Standalone より 52.4% の改善を示しています。
RoadRunner HTTP スループット
DocumentFactory ワーカーパターンを使用した RoadRunner による実 HTTP サーバーベンチマークです。ab (Apache Bench) により計測されました。
| 構成 | Docs/sec | 平均レイテンシ (ms) | p50 (ms) | p99 (ms) | 失敗 |
|---|---|---|---|---|---|
| 1 worker / 1 concurrent | 1,320 | 0.76 | 1 | 1 | 0 |
| 4 workers / 4 concurrent | 4,812 | 0.83 | 1 | 1 | 0 |
HTTP オーバーヘッド vs 生の pcntl_fork:
- シングルスレッド: 49.3% オーバーヘッド (1,320 vs 2,605 docs/sec)
- マルチワーカー: 47.8% オーバーヘッド (4,812 vs 9,221 docs/sec)
約 48% のオーバーヘッドは、完全な HTTP スタック(TCP accept、HTTP parse、レスポンス書き込み)のコストを反映しています。このオーバーヘッドがあっても、RoadRunner 上の TCPDF-Next は、サブミリ秒のレイテンシとゼロの失敗リクエストで、毎秒 4,812 件のリアル HTTP PDF レスポンスを配信します。
計測手法
- 環境: すべてのライブラリは同一の Docker コンテナ(PHP 8.5.3、Debian bookworm)内で同一の設定で実行されます。
- リソース制約: Docker のリソース制約により、コンテナは 4 CPUs と 16 GB RAM に制限されています。
- ランタイム: すべてのライブラリで OPcache と JIT が有効です。公平なタイミングのため、非推奨警告はグローバルに抑制されています。
- サブプロセス分離: 正確なメモリ計測のため、各ライブラリ/シナリオのペアは個別の PHP プロセス(
exec())で実行されます — 他のライブラリのオートローダークラスがmemory_get_peak_usage()を汚染しません。 - API の同等性: TCPDF-Next と TCPDF は非 HTML シナリオでネイティブの
Cell/MultiCellAPI を使用します。DomPDF と mPDF は同等の HTML マークアップ(ネイティブインターフェース)を使用します。 - TrueType フォントテストは、実際のフォント解析コストを明らかにするため DejaVu Sans(約700 KB の
.ttf)を使用します。Helvetica (Base14) テストはオーバーヘッドゼロのベースラインを示します。 - Artisan (Chrome) は、ピクセルパーフェクトな CSS3 レンダリングのため、CDP 経由でヘッドレス Chromium を使用します(CSP により JavaScript は無効化)。
- Artisan フェーズ別は Chrome レンダリングを分解します:フェーズ 1(Chrome CDP
printToPDF)vs フェーズ 2(PdfReader + PageImporter + 埋め込み)。 - ワーカーライフサイクルは、
DocumentFactory(共有 FontRegistry + ロック、ImageRegistry)とcreateStandalone()(呼び出しごとに新しいレジストリ)を比較します。 - ワーカーライフサイクル TTF は、
DocumentFactoryの主要な価値を実証します:数千のワーカーリクエストにわたってキャッシュされた TrueType フォントデータ。 - RoadRunner HTTP は roadrunner-server/roadrunner を
DocumentFactoryワーカーパターンで使用し、ab(Apache Bench) により計測されます。 - ウォームアップ: 計測開始前に3回の反復が実行・破棄され、OPcache と JIT が完全にウォームアップされます。
- 反復回数: シナリオごとに20回計測。外れ値のノイズを排除するため中央値を報告します。
- スループット: テストは30秒間連続で実行されます。
- タイミング:
hrtime(true)がナノ秒精度のウォールクロック計測を提供します。 - メモリ:
memory_get_peak_usage(true)が実際の(RSS)ピークメモリを報告します。 - ファイルサイズ: 出力はディスクにフラッシュされ、
filesize()で計測されます。
データ解釈に関する注意事項
- メモリのサブプロセス分離: 各ライブラリのレイテンシベンチマークは独自の PHP サブプロセスで実行されます。必要なオートローダーのみがロードされるため、
memory_get_peak_usage()はそのライブラリ単体の実際のメモリコストを反映しています — 他のライブラリからの累積的なオートローダー汚染はありません。 - Artisan (Chrome) は BrowserPool キープアライブを使用: Chrome プロセスは反復間で存続し、プロダクション環境の動作(RoadRunner/Swoole/Octane)と一致します。コールドスタートのオーバーヘッド(初回の Chromium 起動に約 250 ms)は除外されています — これは数千のリクエストにわたって償却される一回限りのコストです。
- レイテンシとスループットの差異: 単一実行のレイテンシ計測には
gc_collect_cycles()、memory_reset_peak_usage()、hrtime()のオーバーヘッド(約 0.3 ms)が含まれます。スループットテストは計測オーバーヘッドなしのタイトループで実行されるため、ドキュメントあたりの時間(1000/docs_per_sec)は単一実行の中央値より短くなります。スループット数値はプロダクション環境のパフォーマンスをより正確に反映しています。 - Helvetica vs TrueType: Helvetica はファイル I/O がゼロの組み込み PDF フォント(Base14)です。TrueType シナリオは、
.ttfファイル(約700 KB)の解析を必要とする DejaVu Sans を使用します。DocumentFactoryのキャッシュされた FontRegistry の優位性は TrueType フォントでのみ顕在化します。 - JIT 互換性 (*): * が付いた値は、そのライブラリのコードパスにおける PHP JIT セグメンテーション違反(SIGSEGV、終了コード 139)のため、JIT を無効化(
opcache.jit=0)して計測されました。OPcache のバイトコードキャッシングは有効のままです。これは、特定の複雑なループパターンに影響する PHP JIT の既知のバグクラスです。
ベンチマークの再現
ベンチマークスイートはリポジトリに含まれています。結果を再現するには:
cd benchmark
docker compose up --build結果は実行終了時に stdout に出力されます。Docker セットアップにより、ホスト OS に関係なく同一の環境が保証されます。
関連ドキュメント
- TCPDF からの移行 — ステップバイステップの移行ガイド
- パフォーマンスチューニング — ストリーミングモード、メモリ最適化、キャッシング戦略