效能評測
以下為 TCPDF-Next 與三套主流 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 via 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 頁)
一頁 A4 文件,包含標題與基本格式化文字,使用內建 Helvetica 字型。無圖片、無表格。
| 函式庫 | 時間(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 列表格明細項目、小計、頁首、頁尾及一張標誌圖片。
| 函式庫 | 時間(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 在 34.29 ms 內完成 100 頁報告 — 比 TCPDF 快 3.1x、比 mPDF 快 32.3x、比 DomPDF 快 62.1x。
JIT 相容性說明
*mPDF 的 100 頁報告結果是在停用 JIT(opcache.jit=0)的情況下測量的,因為 mPDF 的程式碼路徑會觸發 PHP JIT 區段錯誤(SIGSEGV,退出碼 139)。OPcache 位元組碼快取仍保持啟用。這是已知的一類 PHP JIT 錯誤,會影響某些複雜迴圈模式。mPDF 其餘所有情境皆在啟用 JIT 的情況下執行。
TrueType 文件(1 頁)
一頁 A4 文件,使用 DejaVu Sans(~700 KB TrueType 字型)。此情境揭露真實的字型檔案解析成本 — 不同於 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 轉 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 轉 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) 管線拆解為兩個階段:
- Chrome CDP 渲染 — 無頭 Chrome 透過
printToPDF將 HTML 轉換為 PDF 位元組 - PDF 匯入 + 嵌入 — TCPDF-Next 解析 Chrome 的 PDF、擷取頁面作為 Form XObject,並嵌入目標文件
| 階段 | 中位數(ms) | 平均值(ms) | 最小值(ms) | 最大值(ms) | 標準差 |
|---|---|---|---|---|---|
| Chrome CDP 渲染 | 81.17 | 81.17 | 65.51 | 95.80 | 4.84 |
| PDF 匯入 + 嵌入 | 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 匯入 = 2.3%
Chrome 的 printToPDF 佔據管線 97.4% 的總時間。PDF 匯入階段(PdfReader + PageImporter + XObject 嵌入)僅增加約 2 ms — 開銷可忽略不計。
標準測試與階段測試的差異
標準 Artisan 測試(66.70 ms)使用整合式 writeHtmlChrome() 方法搭配 BrowserPool keep-alive。階段測試(合計 83.35 ms)分別量測各階段,增加了測量開銷。兩者使用相同的 keep-alive Chrome 實例 — 初始 Chromium 啟動的 ~250 ms 冷啟動成本已排除,因為這是一次性成本,可在數千次請求中攤銷。
何時使用哪種方式
對於簡單 HTML(表格、基本格式化),使用 TCPDF-Next 的內建 HTML 解析器(1.51 ms)。對於需要像素完美保真度的複雜 CSS3 排版(Flexbox、Grid、Web Fonts),使用 Artisan — ~67 ms 的額外開銷換來 Chrome 渲染引擎的完整能力。
Worker 生命週期(DocumentFactory vs Standalone)
TCPDF-Next 提供 DocumentFactory 模式,專為長時間執行的 PHP Worker(RoadRunner、Swoole、Laravel Octane)而設計。Factory 在啟動時預先初始化並鎖定共用的註冊表(FontRegistry、ImageRegistry)。每個 HTTP 請求從 Factory 建立一個輕量、可丟棄的 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 已快取) | 2.60 | 6.0 | 24.5 |
| Standalone(TTF 解析) | 4.09 | 6.0 | 24.3 |
Factory 加速比:1.6x — 快取字型解析每次請求省去約 1.5 ms。在 RoadRunner/Swoole Worker 每分鐘處理 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 轉 PDF 記憶體
| 函式庫 | 峰值記憶體(MB) |
|---|---|
| TCPDF-Next | 4.0 |
| Artisan (Chrome) | 4.0 |
| DomPDF | 10.0 |
| TCPDF | 12.0 |
| mPDF | 18.0 |
Artisan (Chrome) 僅測量 PHP 端記憶體 — 無頭 Chrome 程序擁有由作業系統管理的獨立記憶體空間。
輸出檔案大小
所有數值單位為 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 轉 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 秒。數值反映持續負載下的產生能力。
標準(文件/秒)
| 模式 | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| 單執行緒 | 2,605 | 1,169 | 233 | 130 |
| 4 Workers | 9,221 | 4,163 | 841 | 487 |
每分鐘文件數
| 模式 | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| 單執行緒 | 156,284 | 70,134 | 13,956 | 7,800 |
| 4 Workers | 553,280 | 249,752 | 50,484 | 29,194 |
使用 4 Workers 時,TCPDF-Next 可維持每秒超過 9,200 份文件 — 即每分鐘超過 553,000 份文件。這是 TCPDF 吞吐量的 2.2x、DomPDF 的 11.0x、mPDF 的 19.0x。
Worker 生命週期吞吐量
比較使用內建 Helvetica 字型時 DocumentFactory 與 createStandalone() 的吞吐量。
| 模式 | DocumentFactory | createStandalone() |
|---|---|---|
| 單執行緒 | 2,490 | 2,515 |
| 4 Workers | 9,074 | 9,191 |
使用內建字型時,DocumentFactory 與 createStandalone() 產生相同的吞吐量 — 沒有需要快取的字型解析作業。
TrueType 文件吞吐量
使用 TrueType 字型(DejaVu Sans)的吞吐量 — 揭露各函式庫每次的實際字型解析開銷。
| 函式庫 | 單執行緒(文件/秒) |
|---|---|
| TCPDF-Next | 242 |
| TCPDF | 81 |
| mPDF | 50 |
| DomPDF | 30 |
TCPDF-Next 的 TrueType 吞吐量是 TCPDF 的 3.0x、mPDF 的 4.8x、DomPDF 的 8.0x — 反映其高效的字型子集化與快取機制。
Worker 生命週期 TTF 吞吐量
DocumentFactory(已快取 TrueType 字型資料)vs createStandalone()(每次請求解析 TTF)。
| 模式 | Factory(TTF 已快取) | Standalone(TTF 解析) |
|---|---|---|
| 單執行緒 | 364 | 243 |
| 4 Workers | 1,327 | 871 |
Factory 吞吐量優勢:1.5x(單執行緒)。快取的 TrueType 字型資料消除了每次請求的 .ttf 解析開銷。使用 4 Workers 時,Factory 達到 1,327 文件/秒 — 比 Standalone 提升 52.4%。
RoadRunner HTTP 吞吐量
使用 RoadRunner 搭配 DocumentFactory Worker 模式的實際 HTTP 伺服器評測。透過 ab(Apache Bench)測量。
| 設定 | 文件/秒 | 平均延遲(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 文件/秒)
- 多 Worker:47.8% 開銷(4,812 vs 9,221 文件/秒)
約 48% 的開銷反映了完整 HTTP 堆疊的成本(TCP 接受、HTTP 解析、回應寫入)。即使加上這些開銷,TCPDF-Next 搭配 RoadRunner 仍能達到每秒 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) 使用無頭 Chromium 透過 CDP 進行像素完美的 CSS3 渲染(透過 CSP 停用 JavaScript)。
- Artisan 階段測試 拆解 Chrome 渲染:階段 1(Chrome CDP
printToPDF)vs 階段 2(PdfReader + PageImporter + 嵌入)。 - Worker 生命週期 比較
DocumentFactory(共用 FontRegistry + 鎖定、ImageRegistry)vscreateStandalone()(每次呼叫建立全新註冊表)。 - Worker 生命週期 TTF 展示
DocumentFactory的核心價值:跨數千次 Worker 請求快取 TrueType 字型資料。 - RoadRunner HTTP 使用 roadrunner-server/roadrunner 搭配
DocumentFactoryWorker 模式,透過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 keep-alive: Chrome 程序在各迭代間保持存活,符合生產環境行為(RoadRunner/Swoole/Octane)。冷啟動開銷(初始 Chromium 啟動約 250 ms)已排除 — 這是一次性成本,可在數千次請求中攤銷。
- 延遲與吞吐量的差距: 單次延遲測量包含
gc_collect_cycles()、memory_reset_peak_usage()與hrtime()開銷(約 0.3 ms)。吞吐量測試在緊密迴圈中執行,不含測量開銷,因此其每文件時間(1000/文件每秒)低於單次中位數。吞吐量數據更準確地反映生產環境效能。 - Helvetica vs TrueType: Helvetica 是內建 PDF 字型(Base14),完全無需檔案 I/O。TrueType 情境使用 DejaVu Sans,需要解析
.ttf檔案(~700 KB)。DocumentFactory的快取 FontRegistry 優勢僅在 TrueType 字型時才會顯現。 - JIT 相容性(*): 標示 * 的數值是在停用 JIT(
opcache.jit=0)的情況下測量的,因為該函式庫的程式碼路徑會觸發 PHP JIT 區段錯誤(SIGSEGV,退出碼 139)。OPcache 位元組碼快取仍保持啟用。這是已知的一類 PHP JIT 錯誤,會影響某些複雜迴圈模式。
重現評測
評測套件包含在儲存庫中。若要重現這些結果:
cd benchmark
docker compose up --build結果會在執行結束時輸出至 stdout。Docker 設定確保環境一致,不受主機作業系統影響。
延伸閱讀
- 從 TCPDF 遷移 — 逐步遷移指南
- 效能調校 — 串流模式、記憶體最佳化與快取策略