Benchmarks de rendimiento
Benchmarks de rendimiento del mundo real comparando TCPDF-Next contra tres bibliotecas de PDF PHP establecidas: TCPDF, DomPDF y mPDF. Todas las pruebas se ejecutaron en hardware idéntico bajo condiciones Docker controladas. Los resultados son medianas de 20 iteraciones para eliminar ruido de valores atípicos.
Entorno de pruebas
| Parámetro | Valor |
|---|---|
| CPU | Intel Core i9-13900K (x86-64) |
| RAM | 64 GB DDR5 (Docker limitado a 16 GB) |
| Docker | 4 CPUs, 16 GB RAM, Debian bookworm |
| PHP | 8.5.3 (CLI, OPcache habilitado, JIT habilitado) |
| 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 (pruebas de throughput HTTP) |
| Warmup | 3 iteraciones (descartadas) |
| Medidas | 20 iteraciones (mediana reportada) |
| Temporización | hrtime(true) reloj de pared con precisión de nanosegundos |
Comparación interactiva
PHP 8.5.3 + OPcache + JIT · Docker 4 CPUs / 16 GB · i9-13900K · Median of 20 runs
Velocidad de generación
Cada escenario se ejecutó 20 veces después de 3 iteraciones de warmup. Se reporta el tiempo de generación mediano.
Documento simple (1 página)
Una sola página A4 con un encabezado y texto formateado básico usando fuente Helvetica integrada. Sin imágenes, sin tablas.
| Biblioteca | Tiempo (ms) |
|---|---|
| TCPDF-Next | 0.68 |
| TCPDF | 2.55 |
| DomPDF | 4.16 |
| mPDF | 6.71 |
TCPDF-Next completa el escenario más simple en menos de 1 ms — 3.8x más rápido que TCPDF, 6.1x más rápido que DomPDF y 9.9x más rápido que mPDF.
Factura (2 páginas)
Una factura de dos páginas con 25 filas de líneas de detalle tabulares, subtotales, headers, footers y una imagen de logo.
| Biblioteca | Tiempo (ms) |
|---|---|
| TCPDF | 1.96 |
| TCPDF-Next | 2.01 |
| mPDF | 15.86 |
| DomPDF | 17.33 |
TCPDF-Next y TCPDF están virtualmente empatados en el escenario de factura (~1.0x). Ambos superan significativamente a mPDF (7.9x más lento) y DomPDF (8.6x más lento).
Reporte de 100 páginas
Un documento de 100 páginas con contenido mixto denso: encabezados, párrafos y datos estructurados.
| Biblioteca | Tiempo (ms) |
|---|---|
| TCPDF-Next | 34.29 |
| TCPDF | 105.39 |
| mPDF | 1,106.59* |
| DomPDF | 2,129.12 |
TCPDF-Next completa un reporte de 100 páginas en 34.29 ms — 3.1x más rápido que TCPDF, 32.3x más rápido que mPDF y 62.1x más rápido que DomPDF.
Nota de compatibilidad JIT
*El resultado del reporte de 100 páginas de mPDF fue medido con JIT deshabilitado (opcache.jit=0) debido a un segfault PHP JIT (SIGSEGV, código de salida 139) en la ruta de código de mPDF. El caché de bytecode OPcache permaneció activo. Esta es una clase conocida de bugs JIT de PHP que afectan ciertos patrones de bucle complejos. Todos los demás escenarios de mPDF se ejecutaron con JIT habilitado.
Documento TrueType (1 página)
Una sola página A4 usando DejaVu Sans (~700 KB fuente TrueType). Este escenario expone los costos reales de parseo de archivos de fuente — a diferencia de Helvetica (fuente integrada Base14 que requiere cero I/O de archivo).
| Biblioteca | Tiempo (ms) |
|---|---|
| TCPDF-Next | 4.08 |
| TCPDF | 12.11 |
| mPDF | 16.51 |
| DomPDF | 24.14 |
TCPDF-Next parsea e incrusta la fuente TrueType 3.0x más rápido que TCPDF, 4.0x más rápido que mPDF y 5.9x más rápido que DomPDF.
Velocidad relativa (todos los escenarios)
Todos los valores relativos a TCPDF-Next (línea base 1.0x). Menor = más rápido.
| Escenario | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| Documento simple | 1.0x | 3.8x | 6.1x | 9.9x |
| Factura | 1.0x | ~1.0x | 8.6x | 7.9x |
| Reporte de 100 páginas | 1.0x | 3.1x | 62.1x | 32.3x |
| Documento TrueType | 1.0x | 3.0x | 5.9x | 4.0x |
HTML a PDF
Enfoques de procesamiento HTML
Diferentes bibliotecas toman enfoques fundamentalmente diferentes para convertir HTML a PDF. Entender estas diferencias es esencial para interpretar los resultados de benchmarks:
Traducción directa (TCPDF-Next, TCPDF) — El parser HTML integrado tokeniza los tags HTML y los mapea directamente a comandos de dibujo PDF (Cell, MultiCell, Image) en un solo pase de streaming. Este enfoque es extremadamente rápido pero solo soporta tags HTML básicos y CSS inline — sin Flexbox, sin Grid, sin selectores CSS complejos.
Motor de layout CSS (DomPDF, mPDF) — Estas bibliotecas están diseñadas con HTML como su interfaz principal. DomPDF construye un árbol DOM completo, aplica la cascada CSS (especificidad, herencia) y calcula el layout de modelo de caja antes de renderizar a PDF. El WriteHTML() de mPDF procesa HTML de manera similar a través de su propio motor de layout CSS. Ambos soportan más funcionalidades CSS que los parsers de traducción directa (floats, elementos posicionados, tablas estilizadas) pero aún no alcanzan el CSS3 completo a nivel de navegador.
Renderizado de navegador completo (Artisan / Chrome) — TCPDF-Next Artisan delega el renderizado a Chromium headless via Chrome DevTools Protocol (CDP). Esto proporciona soporte CSS3 pixel-perfect: Flexbox, Grid, Web Fonts, media queries, variables CSS — salida idéntica a lo que un navegador Chrome produciría.
El benchmark compara el enfoque nativo de cada biblioteca: TCPDF-Next y TCPDF usan su parser de traducción directa integrado; DomPDF y mPDF usan sus motores de renderizado CSS (su API principal); Artisan usa Chrome.
Resultados
| Biblioteca | Enfoque | Tiempo (ms) |
|---|---|---|
| TCPDF-Next | Traducción directa | 1.51 |
| TCPDF | Traducción directa | 6.60 |
| DomPDF | Motor de layout CSS | 13.69 |
| mPDF | Motor de layout CSS | 29.63 |
| Artisan (Chrome) | Renderizado de navegador completo | 66.70 |
Tiempo relativo (HTML a PDF)
| Biblioteca | Relativo |
|---|---|
| TCPDF-Next | 1.0x |
| TCPDF | 4.4x |
| DomPDF | 9.0x |
| mPDF | 19.6x |
| Artisan (Chrome) | 44.1x |
El parser de traducción directa de TCPDF-Next entrega rendimiento sub-2 ms — 4.4x más rápido que el parser basado en regex de TCPDF, 9.0x más rápido que el motor de layout CSS de DomPDF y 19.6x más rápido que mPDF. Artisan (Chrome) es 44.1x más lento pero proporciona fidelidad CSS3 completa que ninguna otra biblioteca puede igualar.
Artisan Chrome — Desglose por fases
Descompone el pipeline de Artisan (Chrome) en dos fases:
- Chrome CDP Render — Chrome headless convierte HTML a bytes PDF via
printToPDF - PDF Import + Embed — TCPDF-Next parsea el PDF de Chrome, extrae la página como Form XObject y la embebe en el documento destino
| Fase | Mediana (ms) | Media (ms) | Mín (ms) | Máx (ms) | Stddev |
|---|---|---|---|---|---|
| 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 |
| Total | 83.35 | 83.29 | 68.20 | 97.56 | 4.70 |
Distribución de tiempo: Chrome CDP = 97.4% | PDF Import = 2.3%
El printToPDF de Chrome domina el pipeline con 97.4% del tiempo total. La fase de PDF Import (PdfReader + PageImporter + embedding de XObject) añade solo ~2 ms — overhead despreciable.
Medición estándar vs por fases
La prueba estándar de Artisan (66.70 ms) usa el método integrado writeHtmlChrome() con BrowserPool keep-alive. La prueba por fases (83.35 ms total) instrumenta cada fase por separado, añadiendo overhead de medición. Ambas usan la misma instancia de Chrome keep-alive — el costo de arranque en frío de ~250 ms para el lanzamiento inicial de Chromium se excluye ya que es un costo único amortizado sobre miles de requests.
Cuándo usar cada enfoque
Para HTML simple (tablas, formato básico), usa el parser HTML integrado de TCPDF-Next (1.51 ms). Para layouts CSS3 complejos que requieren fidelidad pixel-perfect (Flexbox, Grid, Web Fonts), usa Artisan — el overhead de ~67 ms compra toda la potencia del motor de renderizado de Chrome.
Ciclo de vida del worker (DocumentFactory vs Standalone)
TCPDF-Next proporciona un patrón DocumentFactory diseñado para workers PHP de larga duración (RoadRunner, Swoole, Laravel Octane). El factory pre-inicializa y bloquea registros compartidos (FontRegistry, ImageRegistry) en el momento de arranque. Cada request HTTP crea un Document ligero y desechable desde el factory — eliminando el overhead de inicialización por request.
Esta sección compara DocumentFactory (registros compartidos y bloqueados) contra createStandalone() (registros frescos por llamada).
Fuentes integradas (Helvetica)
| Modo | Mediana (ms) | Memoria pico (MB) | Tamaño archivo (KB) |
|---|---|---|---|
| DocumentFactory | 0.60 | 4.0 | 3.3 |
| createStandalone() | 0.70 | 4.0 | 3.3 |
Resultado: ~equivalente (ratio 0.86x). Con fuentes integradas (Helvetica), ambos modos rinden de forma idéntica porque no hay parseo de archivos de fuente que cachear. La ventaja real de DocumentFactory aparece con fuentes TrueType.
Fuentes TrueType (DejaVu Sans)
Esta es la prueba clave para la propuesta de valor de DocumentFactory. A diferencia de la prueba de Helvetica anterior (fuente integrada, cero parseo), esta prueba usa DejaVu Sans (~700 KB fuente TrueType). DocumentFactory pre-registra y cachea los datos de fuente parseados en el arranque — los requests subsiguientes omiten toda la I/O de archivos de fuente. createStandalone() debe parsear el archivo .ttf en cada request individual.
| Modo | Mediana (ms) | Memoria pico (MB) | Tamaño archivo (KB) |
|---|---|---|---|
| Factory (TTF cacheado) | 2.60 | 6.0 | 24.5 |
| Standalone (TTF parseo) | 4.09 | 6.0 | 24.3 |
Aceleración del factory: 1.6x — El parseo de fuentes cacheado elimina ~1.5 ms por request. En un worker RoadRunner/Swoole manejando 1,000 requests/minuto, esto ahorra ~25 segundos de tiempo de CPU por minuto.
Uso de memoria pico
Todos los valores en MB (mediana). El benchmark de cada biblioteca se ejecuta en su propio subproceso PHP — solo se carga el autoloader requerido, así que memory_get_peak_usage() refleja el costo real de memoria de esa biblioteca sola.
Escenarios estándar
| Escenario | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| Documento simple | 4.0 | 12.0 | 6.0 | 14.0 |
| Factura | 4.0 | 12.0 | 12.0 | 14.0 |
| Reporte de 100 páginas | 4.0 | 12.0 | 66.0 | 27.9* |
| Documento TrueType | 6.0 | 14.0 | 20.0 | 16.0 |
TCPDF-Next mantiene una huella consistente de 4 MB desde documentos de 1 página hasta 100 páginas, demostrando gestión eficiente de memoria a través de objetos de página compactados y referencias a recursos compartidos.
Memoria HTML a PDF
| Biblioteca | Memoria pico (MB) |
|---|---|
| TCPDF-Next | 4.0 |
| Artisan (Chrome) | 4.0 |
| DomPDF | 10.0 |
| TCPDF | 12.0 |
| mPDF | 18.0 |
Artisan (Chrome) mide solo la memoria del lado PHP — el proceso de Chrome headless tiene su propio espacio de memoria gestionado por el SO.
Tamaño de archivo de salida
Todos los valores en KB (mediana).
Escenarios estándar
| Escenario | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| Documento simple | 3.3 | 7.1 | 1.7 | 28.0 |
| Factura | 5.0 | 9.2 | 4.0 | 30.2 |
| Reporte de 100 páginas | 96.4 | 100.8 | 128.7 | 181.1* |
| Documento TrueType | 24.7 | 101.3 | 16.1 | 42.4 |
DomPDF produce los archivos más pequeños para documentos simples (1.7 KB) a través de optimización agresiva del content stream. TCPDF-Next produce salida compacta a través de cross-reference streams y object streams de PDF 2.0. TCPDF embebe un subset de fuente TrueType significativamente más grande (101.3 KB vs 24.7 KB).
Tamaño de archivo HTML a PDF
| Biblioteca | Tamaño archivo (KB) |
|---|---|
| DomPDF | 5.3 |
| TCPDF-Next | 6.6 |
| TCPDF | 12.6 |
| Artisan (Chrome) | 36.9 |
| mPDF | 46.0 |
La salida de Artisan (Chrome) es más grande (36.9 KB) porque el printToPDF de Chrome genera un PDF completo independiente con recursos embebidos.
Throughput
Las pruebas de throughput se ejecutan continuamente durante 30 segundos usando el escenario de documento simple. Los valores reflejan la capacidad de generación sostenida bajo carga.
Estándar (docs/seg)
| Modo | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| Hilo único | 2,605 | 1,169 | 233 | 130 |
| 4 Workers | 9,221 | 4,163 | 841 | 487 |
Documentos por minuto
| Modo | TCPDF-Next | TCPDF | DomPDF | mPDF |
|---|---|---|---|---|
| Hilo único | 156,284 | 70,134 | 13,956 | 7,800 |
| 4 Workers | 553,280 | 249,752 | 50,484 | 29,194 |
Con 4 workers, TCPDF-Next sostiene más de 9,200 documentos por segundo — más de 553,000 documentos por minuto. Esto es 2.2x el throughput de TCPDF, 11.0x de DomPDF y 19.0x de mPDF.
Throughput de ciclo de vida del worker
Compara DocumentFactory vs createStandalone() throughput usando fuente Helvetica integrada.
| Modo | DocumentFactory | createStandalone() |
|---|---|---|
| Hilo único | 2,490 | 2,515 |
| 4 Workers | 9,074 | 9,191 |
Con fuentes integradas, DocumentFactory y createStandalone() producen throughput equivalente — sin parseo de fuentes que cachear.
Throughput de documento TrueType
Throughput con fuentes TrueType (DejaVu Sans) — expone el overhead real de parseo de fuentes por biblioteca.
| Biblioteca | Hilo único (docs/seg) |
|---|---|
| TCPDF-Next | 242 |
| TCPDF | 81 |
| mPDF | 50 |
| DomPDF | 30 |
El throughput TrueType de TCPDF-Next es 3.0x TCPDF, 4.8x mPDF y 8.0x DomPDF — reflejando su eficiente subsetting y caché de fuentes.
Throughput TTF de ciclo de vida del worker
DocumentFactory (datos de fuente TrueType cacheados) vs createStandalone() (parsear TTF en cada request).
| Modo | Factory (TTF cacheado) | Standalone (TTF parseo) |
|---|---|---|
| Hilo único | 364 | 243 |
| 4 Workers | 1,327 | 871 |
Ventaja de throughput del factory: 1.5x (hilo único). Los datos de fuente TrueType cacheados eliminan el overhead de parseo de .ttf por request. Con 4 workers, el factory logra 1,327 docs/seg — una mejora del 52.4% sobre standalone.
Throughput HTTP RoadRunner
Benchmark de servidor HTTP real usando RoadRunner con patrón de worker DocumentFactory. Medido via ab (Apache Bench).
| Configuración | Docs/seg | Latencia media (ms) | p50 (ms) | p99 (ms) | Fallidos |
|---|---|---|---|---|---|
| 1 worker / 1 concurrente | 1,320 | 0.76 | 1 | 1 | 0 |
| 4 workers / 4 concurrentes | 4,812 | 0.83 | 1 | 1 | 0 |
Overhead HTTP vs pcntl_fork raw:
- Hilo único: 49.3% overhead (1,320 vs 2,605 docs/seg)
- Multi-worker: 47.8% overhead (4,812 vs 9,221 docs/seg)
El overhead de ~48% refleja el costo del stack HTTP completo (TCP accept, HTTP parse, escritura de respuesta). Incluso con este overhead, TCPDF-Next detrás de RoadRunner entrega 4,812 respuestas HTTP PDF reales por segundo con latencia sub-milisegundo y cero requests fallidos.
Metodología
- Entorno: Todas las bibliotecas se ejecutan dentro del mismo contenedor Docker (PHP 8.5.3, Debian bookworm) con configuración idéntica.
- Restricciones de recursos: Contenedor limitado a 4 CPUs y 16 GB RAM via restricciones de recursos Docker.
- Runtime: OPcache y JIT habilitados para todas las bibliotecas. Advertencias de deprecación suprimidas globalmente para temporización justa.
- Aislamiento por subproceso: Cada par biblioteca/escenario se ejecuta en un proceso PHP separado (
exec()) para medición de memoria precisa — las clases del autoloader de otras bibliotecas no contaminanmemory_get_peak_usage(). - Paridad de API: TCPDF-Next y TCPDF usan la API nativa
Cell/MultiCellpara escenarios no HTML. DomPDF y mPDF usan markup HTML equivalente (su interfaz nativa). - Prueba de fuente TrueType usa DejaVu Sans (~700 KB
.ttf) para exponer costos reales de parseo de fuentes; las pruebas de Helvetica (Base14) muestran la línea base de cero overhead. - Artisan (Chrome) usa Chromium headless via CDP para renderizado CSS3 pixel-perfect (JavaScript deshabilitado via CSP).
- Artisan por fases descompone el renderizado de Chrome: Fase 1 (Chrome CDP
printToPDF) vs Fase 2 (PdfReader + PageImporter + embed). - Ciclo de vida del worker compara
DocumentFactory(FontRegistry compartido + lock, ImageRegistry) vscreateStandalone()(registros frescos por llamada). - Ciclo de vida del worker TTF demuestra el valor clave de
DocumentFactory: datos de fuente TrueType cacheados a través de miles de requests del worker. - RoadRunner HTTP usa roadrunner-server/roadrunner con patrón de worker
DocumentFactory, medido viaab(Apache Bench). - Warmup: Se ejecutan y descartan 3 iteraciones antes de comenzar la medición, asegurando que OPcache y JIT estén completamente precalentados.
- Iteraciones: 20 iteraciones medidas por escenario. Se reporta la mediana para eliminar ruido de valores atípicos.
- Throughput: Las pruebas se ejecutan continuamente durante 30 segundos.
- Temporización:
hrtime(true)proporciona medición de reloj de pared con precisión de nanosegundos. - Memoria:
memory_get_peak_usage(true)reporta la memoria pico real (RSS). - Tamaño de archivo: La salida se escribe a disco y se mide con
filesize().
Notas sobre interpretación de datos
- Aislamiento por subproceso para memoria: El benchmark de latencia de cada biblioteca se ejecuta en su propio subproceso PHP. Solo se carga el autoloader requerido, así que
memory_get_peak_usage()refleja el costo real de memoria de esa biblioteca sola — no contaminación acumulativa del autoloader de otras bibliotecas. - Artisan (Chrome) usa BrowserPool keep-alive: El proceso de Chrome permanece activo entre iteraciones, igualando el comportamiento en producción (RoadRunner/Swoole/Octane). El overhead de arranque en frío (~250 ms para el lanzamiento inicial de Chromium) se excluye — es un costo único amortizado sobre miles de requests.
- Brecha latencia vs throughput: Las mediciones de latencia de ejecución única incluyen overhead de
gc_collect_cycles(),memory_reset_peak_usage()yhrtime()(~0.3 ms). Las pruebas de throughput se ejecutan en un bucle ajustado sin overhead de medición, así que su tiempo por documento (1000/docs_por_seg) es menor que la mediana de ejecución única. Los números de throughput reflejan más precisamente el rendimiento en producción. - Helvetica vs TrueType: Helvetica es una fuente PDF integrada (Base14) que requiere cero I/O de archivo. El escenario TrueType usa DejaVu Sans, que requiere parsear archivos
.ttf(~700 KB). La ventaja del FontRegistry cacheado deDocumentFactorysolo se manifiesta con fuentes TrueType. - Compatibilidad JIT (*): Los valores marcados con * fueron medidos con JIT deshabilitado (
opcache.jit=0) debido a un segfault PHP JIT (SIGSEGV, código de salida 139) en la ruta de código de esa biblioteca. El caché de bytecode OPcache permanece activo. Esta es una clase conocida de bugs JIT de PHP que afectan ciertos patrones de bucle complejos.
Reproducir los benchmarks
La suite de benchmarks está incluida en el repositorio. Para reproducir estos resultados:
cd benchmark
docker compose up --buildLos resultados se imprimen a stdout al final de la ejecución. La configuración Docker asegura un entorno idéntico independientemente del SO del host.
Lectura adicional
- Migración desde TCPDF — Guía de migración paso a paso
- Optimización de rendimiento — Modo streaming, optimización de memoria y estrategias de caché