Skip to content

性能基准测试

真实的性能基准测试,将 TCPDF-Next 与三个成熟的 PHP PDF 库进行对比:TCPDFDomPDFmPDF。所有测试均在相同的硬件上、受控的 Docker 环境中执行。结果取 20 次迭代的中位数,以消除离群值噪声。

测试环境

参数数值
CPUIntel Core i9-13900K (x86-64)
内存64 GB DDR5(Docker 限制为 16 GB)
Docker4 CPUs, 16 GB RAM, Debian bookworm
PHP8.5.3(CLI,OPcache 启用,JIT 启用)
TCPDF-Next1.7.0
TCPDF6.10.1
DomPDFv3.1.4
mPDFv8.2.7
Artisan (Chrome)通过 CDP 调用 Headless Chromium
RoadRunnerspiral/roadrunner-http ^3.6(HTTP 吞吐量测试)
预热3 次迭代(丢弃)
测量20 次迭代(报告中位数)
计时方式hrtime(true) 纳秒精度挂钟时间

交互式对比

▼ Lower is better
TCPDF-Next
0.68 ms
TCPDF
2.55 ms3.7x
DomPDF
4.16 ms6.1x
mPDF
6.71 ms9.9x

PHP 8.5.3 + OPcache + JIT · Docker 4 CPUs / 16 GB · i9-13900K · Median of 20 runs

生成速度

每个场景在 3 次预热迭代后运行 20 次,报告中位数生成时间。

简单文档(1 页)

一页 A4 文档,包含标题和基本格式化文字,使用内置 Helvetica 字体。无图片、无表格。

时间(ms)
TCPDF-Next0.68
TCPDF2.55
DomPDF4.16
mPDF6.71

TCPDF-Next 在最简单的场景中以不到 1 ms 完成——比 TCPDF 快 3.8x,比 DomPDF 快 6.1x,比 mPDF 快 9.9x。

发票(2 页)

两页发票文档,包含 25 行表格行项目、小计、页眉、页脚和一张 Logo 图片。

时间(ms)
TCPDF1.96
TCPDF-Next2.01
mPDF15.86
DomPDF17.33

TCPDF-Next 与 TCPDF 在发票场景中几乎持平(~1.0x)。两者均显著优于 mPDF(慢 7.9x)和 DomPDF(慢 8.6x)。

100 页报告

100 页文档,包含密集的混合内容:标题、段落和结构化数据。

时间(ms)
TCPDF-Next34.29
TCPDF105.39
mPDF1,106.59*
DomPDF2,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-Next4.08
TCPDF12.11
mPDF16.51
DomPDF24.14

TCPDF-Next 解析和嵌入 TrueType 字体的速度比 TCPDF 快 3.0x,比 mPDF 快 4.0x,比 DomPDF 快 5.9x。

相对速度(所有场景)

所有数值相对于 TCPDF-Next(1.0x 基线)。数值越低越快。

场景TCPDF-NextTCPDFDomPDFmPDF
简单文档1.0x3.8x6.1x9.9x
发票1.0x~1.0x8.6x7.9x
100 页报告1.0x3.1x62.1x32.3x
TrueType 文档1.0x3.0x5.9x4.0x

HTML 转 PDF

HTML 处理方式

不同的库采用根本不同的方式将 HTML 转换为 PDF。理解这些差异对于解读基准测试结果至关重要:

直译(TCPDF-Next、TCPDF) —— 内置 HTML 解析器对 HTML 标签进行词法分析,并在单次流式处理中将其直接映射为 PDF 绘制命令(CellMultiCellImage)。这种方式速度极快,但仅支持基本 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) 调用的 Headless Chromium。这提供了像素级精确的 CSS3 支持:Flexbox、Grid、Web 字体、媒体查询、CSS 变量——输出与 Chrome 浏览器渲染的结果完全一致。

基准测试对比的是每个库的原生方式:TCPDF-Next 和 TCPDF 使用其内置的直译解析器;DomPDF 和 mPDF 使用其 CSS 渲染引擎(即其主要 API);Artisan 使用 Chrome。

结果

处理方式时间(ms)
TCPDF-Next直译1.51
TCPDF直译6.60
DomPDFCSS 排版引擎13.69
mPDFCSS 排版引擎29.63
Artisan (Chrome)完整浏览器渲染66.70

相对时间(HTML 转 PDF)

相对倍数
TCPDF-Next1.0x
TCPDF4.4x
DomPDF9.0x
mPDF19.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) 管线分解为两个阶段:

  1. Chrome CDP 渲染 —— Headless Chrome 通过 printToPDF 将 HTML 转换为 PDF 字节
  2. PDF 导入 + 嵌入 —— TCPDF-Next 解析 Chrome 的 PDF,将页面提取为 Form XObject,并嵌入到目标文档中
阶段中位数(ms)均值(ms)最小值(ms)最大值(ms)标准差
Chrome CDP 渲染81.1781.1765.5195.804.84
PDF 导入 + 嵌入1.962.081.602.870.40
合计83.3583.2968.2097.564.70

时间分布: Chrome CDP = 97.4% | PDF 导入 = 2.3%

Chrome 的 printToPDF 占据了管线 97.4% 的时间。PDF 导入阶段(PdfReader + PageImporter + XObject 嵌入)仅增加约 2 ms——可忽略不计的开销。

标准测试与分阶段测试的说明

标准 Artisan 测试(66.70 ms)使用集成的 writeHtmlChrome() 方法并启用 BrowserPool 长连接。分阶段测试(总计 83.35 ms)对每个阶段单独计时,增加了测量开销。两者均使用相同的 Chrome 长连接实例——初始 Chromium 启动的约 250 ms 冷启动成本被排除在外,因为它是一次性成本,可在数千次请求中分摊。

何时使用哪种方式

对于简单 HTML(表格、基本格式化),使用 TCPDF-Next 的内置 HTML 解析器(1.51 ms)。对于需要像素级精确保真度的复杂 CSS3 布局(Flexbox、Grid、Web 字体),使用 Artisan——约 67 ms 的开销换来 Chrome 渲染引擎的完整能力。

Worker 生命周期(DocumentFactory 与 Standalone)

TCPDF-Next 提供了 DocumentFactory 模式,专为长时间运行的 PHP Worker(RoadRunner、Swoole、Laravel Octane)设计。工厂在启动时预初始化并锁定共享注册表(FontRegistryImageRegistry)。每个 HTTP 请求从工厂创建一个轻量级的一次性 Document——消除了逐请求的初始化开销。

本节将 DocumentFactory(共享、锁定的注册表)与 createStandalone()(每次调用创建全新的注册表)进行对比。

内置字体(Helvetica)

模式中位数(ms)峰值内存(MB)文件大小(KB)
DocumentFactory0.604.03.3
createStandalone()0.704.03.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.606.024.5
Standalone(TTF 解析)4.096.024.3

Factory 加速比:1.6x —— 缓存字体解析消除了每次请求约 1.5 ms 的开销。在处理 1,000 请求/分钟的 RoadRunner/Swoole Worker 中,这意味着每分钟节省约 25 秒的 CPU 时间。

峰值内存使用

所有数值单位为 MB(中位数)。每个库的基准测试在独立的 PHP 子进程中运行——仅加载所需的自动加载器,因此 memory_get_peak_usage() 反映的是该库单独的实际内存成本。

标准场景

场景TCPDF-NextTCPDFDomPDFmPDF
简单文档4.012.06.014.0
发票4.012.012.014.0
100 页报告4.012.066.027.9*
TrueType 文档6.014.020.016.0

TCPDF-Next 从 1 页到 100 页文档始终保持 4 MB 的内存占用,通过紧凑的页面对象和共享资源引用展现了高效的内存管理。

HTML 转 PDF 内存

峰值内存(MB)
TCPDF-Next4.0
Artisan (Chrome)4.0
DomPDF10.0
TCPDF12.0
mPDF18.0

Artisan (Chrome) 仅测量 PHP 端内存——Headless Chrome 进程有其独立的内存空间,由操作系统管理。

输出文件大小

所有数值单位为 KB(中位数)。

标准场景

场景TCPDF-NextTCPDFDomPDFmPDF
简单文档3.37.11.728.0
发票5.09.24.030.2
100 页报告96.4100.8128.7181.1*
TrueType 文档24.7101.316.142.4

DomPDF 通过激进的内容流优化为简单文档生成最小的文件(1.7 KB)。TCPDF-Next 通过 PDF 2.0 交叉引用流和对象流生成紧凑的输出。TCPDF 嵌入了显著更大的 TrueType 字体子集(101.3 KB vs 24.7 KB)。

HTML 转 PDF 文件大小

文件大小(KB)
DomPDF5.3
TCPDF-Next6.6
TCPDF12.6
Artisan (Chrome)36.9
mPDF46.0

Artisan (Chrome) 的输出较大(36.9 KB),因为 Chrome 的 printToPDF 生成的是包含嵌入资源的完整独立 PDF。

吞吐量

吞吐量测试使用简单文档场景持续运行 30 秒。数值反映了负载下的持续生成能力。

标准吞吐量(文档/秒)

模式TCPDF-NextTCPDFDomPDFmPDF
单线程2,6051,169233130
4 Workers9,2214,163841487

每分钟文档数

模式TCPDF-NextTCPDFDomPDFmPDF
单线程156,28470,13413,9567,800
4 Workers553,280249,75250,48429,194

使用 4 个 Worker 时,TCPDF-Next 可维持每秒超过 9,200 份文档——每分钟超过 553,000 份文档。这是 TCPDF 吞吐量的 2.2 倍、DomPDF 的 11.0 倍、mPDF 的 19.0 倍。

Worker 生命周期吞吐量

对比使用内置 Helvetica 字体时 DocumentFactorycreateStandalone() 的吞吐量。

模式DocumentFactorycreateStandalone()
单线程2,4902,515
4 Workers9,0749,191

使用内置字体时,DocumentFactorycreateStandalone() 产生等效的吞吐量——没有字体解析需要缓存。

TrueType 文档吞吐量

使用 TrueType 字体(DejaVu Sans)的吞吐量——暴露每个库真实的字体解析开销。

单线程(文档/秒)
TCPDF-Next242
TCPDF81
mPDF50
DomPDF30

TCPDF-Next 的 TrueType 吞吐量是 TCPDF 的 3.0 倍、mPDF 的 4.8 倍、DomPDF 的 8.0 倍——反映了其高效的字体子集化和缓存机制。

Worker 生命周期 TTF 吞吐量

DocumentFactory(缓存 TrueType 字体数据)与 createStandalone()(每次请求都解析 TTF)的对比。

模式Factory(TTF 缓存)Standalone(TTF 解析)
单线程364243
4 Workers1,327871

Factory 吞吐量优势:1.5x(单线程)。缓存的 TrueType 字体数据消除了逐请求的 .ttf 解析开销。使用 4 个 Worker 时,Factory 达到 1,327 文档/秒——比 Standalone 提升 52.4%。

RoadRunner HTTP 吞吐量

使用 RoadRunner 结合 DocumentFactory Worker 模式的真实 HTTP 服务器基准测试。通过 ab (Apache Bench) 测量。

配置文档/秒平均延迟(ms)p50(ms)p99(ms)失败数
1 worker / 1 并发1,3200.76110
4 workers / 4 并发4,8120.83110

HTTP 开销(与原始 pcntl_fork 对比):

  • 单线程:49.3% 开销(1,320 vs 2,605 文档/秒)
  • 多 Worker:47.8% 开销(4,812 vs 9,221 文档/秒)

约 48% 的开销来自完整 HTTP 协议栈的成本(TCP accept、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/MultiCell API。DomPDF 和 mPDF 使用等效的 HTML 标记(其原生接口)。
  • TrueType 字体测试 使用 DejaVu Sans(约 700 KB .ttf)以暴露真实的字体解析成本;Helvetica(Base14)测试展示零开销基线。
  • Artisan (Chrome) 使用通过 CDP 调用的 Headless Chromium 实现像素级精确的 CSS3 渲染(通过 CSP 禁用 JavaScript)。
  • Artisan 分阶段测试 将 Chrome 渲染分解为:阶段 1(Chrome CDP printToPDF)vs 阶段 2(PdfReader + PageImporter + 嵌入)。
  • Worker 生命周期 对比 DocumentFactory(共享 FontRegistry + 锁定、ImageRegistry)与 createStandalone()(每次调用创建全新注册表)。
  • Worker 生命周期 TTF 展示 DocumentFactory 的核心价值:跨数千次 Worker 请求缓存 TrueType 字体数据。
  • RoadRunner HTTP 使用 roadrunner-server/roadrunner 结合 DocumentFactory Worker 模式,通过 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/每秒文档数)低于单次运行的中位数。吞吐量数字更准确地反映了生产环境性能。
  • Helvetica 与 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 缺陷,影响某些复杂的循环模式。

重现基准测试

基准测试套件包含在仓库中。要重现这些结果:

bash
cd benchmark
docker compose up --build

结果将在运行结束时打印到标准输出。Docker 配置确保无论宿主操作系统如何,环境都是一致的。

延伸阅读

以 LGPL-3.0-or-later 许可证发布。