Skip to content

성능 벤치마크

TCPDF-Next를 세 가지 주요 PHP PDF 라이브러리인 TCPDF, DomPDF, mPDF와 비교한 실제 성능 벤치마크입니다. 모든 테스트는 동일한 하드웨어에서 통제된 Docker 환경 하에 실행되었습니다. 결과는 이상값 노이즈를 제거하기 위해 20회 반복의 중앙값으로 보고합니다.

테스트 환경

ParameterValue
CPUIntel Core i9-13900K (x86-64)
RAM64 GB DDR5 (Docker limited to 16 GB)
Docker4 CPUs, 16 GB RAM, Debian bookworm
PHP8.5.3 (CLI, OPcache enabled, JIT enabled)
TCPDF-Next1.7.0
TCPDF6.10.1
DomPDFv3.1.4
mPDFv8.2.7
Artisan (Chrome)Headless Chromium via CDP
RoadRunnerspiral/roadrunner-http ^3.6 (HTTP 처리량 테스트)
Warmup3 iterations (discarded)
Measured20 iterations (median reported)
Timinghrtime(true) nanosecond-precision wall clock

인터랙티브 비교

▼ 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 페이지)

내장 Helvetica 폰트를 사용하여 제목과 기본 서식이 적용된 텍스트가 포함된 단일 A4 페이지입니다. 이미지와 테이블은 포함되지 않습니다.

LibraryTime (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행의 표 형식 항목, 소계, 머리글, 바닥글 및 로고 이미지가 포함된 2페이지 인보이스입니다.

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

TCPDF-Next와 TCPDF는 인보이스 시나리오에서 사실상 동일한 성능을 보입니다 (~1.0x). 두 라이브러리 모두 mPDF (7.9x 느림)와 DomPDF (8.6x 느림)를 크게 앞서고 있습니다.

100페이지 보고서

제목, 단락, 구조화된 데이터 등 밀도 높은 혼합 콘텐츠가 포함된 100페이지 문서입니다.

LibraryTime (ms)
TCPDF-Next34.29
TCPDF105.39
mPDF1,106.59*
DomPDF2,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 페이지입니다. 이 시나리오는 실제 폰트 파일 파싱 비용을 노출합니다 — 파일 I/O가 전혀 필요하지 않는 Helvetica(내장 Base14 폰트)와는 다릅니다.

LibraryTime (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 베이스라인)입니다. 낮을수록 빠릅니다.

ScenarioTCPDF-NextTCPDFDomPDFmPDF
Simple Document1.0x3.8x6.1x9.9x
Invoice1.0x~1.0x8.6x7.9x
100-Page Report1.0x3.1x62.1x32.3x
TrueType Document1.0x3.0x5.9x4.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)을 통해 렌더링을 headless Chromium에 위임합니다. 이를 통해 픽셀 퍼펙트 CSS3 지원을 제공합니다: Flexbox, Grid, Web Fonts, 미디어 쿼리, CSS 변수 — Chrome 브라우저가 생성하는 것과 동일한 출력을 제공합니다.

벤치마크는 각 라이브러리의 기본 방식을 비교합니다: TCPDF-Next와 TCPDF는 내장 직접 변환 파서를, DomPDF와 mPDF는 CSS 렌더링 엔진(주요 API)을, Artisan은 Chrome을 사용합니다.

결과

LibraryApproachTime (ms)
TCPDF-NextDirect translation1.51
TCPDFDirect translation6.60
DomPDFCSS layout engine13.69
mPDFCSS layout engine29.63
Artisan (Chrome)Full browser rendering66.70

상대 시간 (HTML to PDF)

LibraryRelative
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 Render — headless Chrome이 printToPDF를 통해 HTML을 PDF 바이트로 변환합니다
  2. PDF Import + Embed — TCPDF-Next가 Chrome의 PDF를 파싱하고, 페이지를 Form XObject로 추출한 다음 대상 문서에 임베딩합니다
PhaseMedian (ms)Mean (ms)Min (ms)Max (ms)Stddev
Chrome CDP Render81.1781.1765.5195.804.84
PDF Import + Embed1.962.081.602.870.40
Total83.3583.2968.2097.564.70

시간 분포: Chrome CDP = 97.4% | PDF Import = 2.3%

Chrome의 printToPDF가 전체 시간의 97.4%를 차지하며 파이프라인을 지배합니다. PDF Import 단계(PdfReader + PageImporter + XObject 임베딩)는 약 2 ms만 추가되며 — 무시할 수 있는 수준의 오버헤드입니다.

표준 vs 단계별 측정

표준 Artisan 테스트(66.70 ms)는 BrowserPool keep-alive가 적용된 통합 writeHtmlChrome() 메서드를 사용합니다. 단계별 테스트(총 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 렌더링 엔진의 모든 기능을 활용할 수 있습니다.

워커 라이프사이클 (DocumentFactory vs Standalone)

TCPDF-Next는 장기 실행 PHP 워커(RoadRunner, Swoole, Laravel Octane)를 위해 설계된 DocumentFactory 패턴을 제공합니다. 팩토리는 부팅 시 공유 레지스트리(FontRegistry, ImageRegistry)를 사전 초기화하고 잠급니다. 각 HTTP 요청은 팩토리에서 경량의 일회용 Document를 생성합니다 — 요청별 초기화 오버헤드를 제거합니다.

이 섹션에서는 DocumentFactory(공유, 잠긴 레지스트리)와 createStandalone()(호출마다 새 레지스트리)를 비교합니다.

내장 폰트 (Helvetica)

ModeMedian (ms)Peak Memory (MB)File Size (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 파일을 파싱해야 합니다.

ModeMedian (ms)Peak Memory (MB)File Size (KB)
Factory (TTF cached)2.606.024.5
Standalone (TTF parse)4.096.024.3

Factory 속도 향상: 1.6x — 캐싱된 폰트 파싱이 요청당 약 1.5 ms를 절약합니다. 분당 1,000건의 요청을 처리하는 RoadRunner/Swoole 워커에서 이는 분당 약 25초의 CPU 시간을 절약합니다.

최대 메모리 사용량

모든 값은 MB(중앙값)입니다. 각 라이브러리의 벤치마크는 자체 PHP 서브프로세스에서 실행됩니다 — 필요한 오토로더만 로드되므로 memory_get_peak_usage()는 해당 라이브러리만의 실제 메모리 비용을 반영합니다.

표준 시나리오

ScenarioTCPDF-NextTCPDFDomPDFmPDF
Simple Document4.012.06.014.0
Invoice4.012.012.014.0
100-Page Report4.012.066.027.9*
TrueType Document6.014.020.016.0

TCPDF-Next는 1페이지에서 100페이지 문서까지 일관된 4 MB 메모리 사용량을 유지하며, 압축된 페이지 객체와 공유 리소스 참조를 통한 효율적인 메모리 관리를 보여줍니다.

HTML to PDF 메모리

LibraryPeak Memory (MB)
TCPDF-Next4.0
Artisan (Chrome)4.0
DomPDF10.0
TCPDF12.0
mPDF18.0

Artisan (Chrome)은 PHP 측 메모리만 측정합니다 — headless Chrome 프로세스는 OS가 관리하는 자체 메모리 공간을 가집니다.

출력 파일 크기

모든 값은 KB(중앙값)입니다.

표준 시나리오

ScenarioTCPDF-NextTCPDFDomPDFmPDF
Simple Document3.37.11.728.0
Invoice5.09.24.030.2
100-Page Report96.4100.8128.7181.1*
TrueType Document24.7101.316.142.4

DomPDF는 적극적인 콘텐츠 스트림 최적화를 통해 간단한 문서에서 가장 작은 파일(1.7 KB)을 생성합니다. TCPDF-Next는 PDF 2.0 교차 참조 스트림과 객체 스트림을 통해 컴팩트한 출력을 생성합니다. TCPDF는 상당히 큰 TrueType 폰트 서브셋을 임베딩합니다(101.3 KB vs 24.7 KB).

HTML to PDF 파일 크기

LibraryFile Size (KB)
DomPDF5.3
TCPDF-Next6.6
TCPDF12.6
Artisan (Chrome)36.9
mPDF46.0

Artisan (Chrome) 출력이 더 큰 이유(36.9 KB)는 Chrome의 printToPDF가 임베딩된 리소스를 포함한 완전한 독립형 PDF를 생성하기 때문입니다.

처리량

처리량 테스트는 간단한 문서 시나리오를 사용하여 30초 동안 연속 실행됩니다. 값은 부하 하에서의 지속적인 생성 용량을 반영합니다.

표준 (docs/sec)

ModeTCPDF-NextTCPDFDomPDFmPDF
Single Thread2,6051,169233130
4 Workers9,2214,163841487

분당 문서 수

ModeTCPDF-NextTCPDFDomPDFmPDF
Single Thread156,28470,13413,9567,800
4 Workers553,280249,75250,48429,194

4개 워커 사용 시, TCPDF-Next는 초당 9,200건 이상의 문서분당 553,000건 이상의 문서를 지속적으로 처리합니다. 이는 TCPDF의 2.2x, DomPDF의 11.0x, mPDF의 19.0x에 해당하는 처리량입니다.

워커 라이프사이클 처리량

내장 Helvetica 폰트를 사용하여 DocumentFactorycreateStandalone()의 처리량을 비교합니다.

ModeDocumentFactorycreateStandalone()
Single Thread2,4902,515
4 Workers9,0749,191

내장 폰트 사용 시 DocumentFactorycreateStandalone()은 동등한 처리량을 보입니다 — 캐싱할 폰트 파싱이 없습니다.

TrueType 문서 처리량

TrueType 폰트(DejaVu Sans) 사용 시의 처리량입니다 — 라이브러리별 실제 폰트 파싱 오버헤드를 노출합니다.

LibrarySingle Thread (docs/sec)
TCPDF-Next242
TCPDF81
mPDF50
DomPDF30

TCPDF-Next의 TrueType 처리량은 TCPDF의 3.0x, mPDF의 4.8x, DomPDF의 8.0x입니다 — 효율적인 폰트 서브셋팅과 캐싱을 반영합니다.

워커 라이프사이클 TTF 처리량

DocumentFactory(캐싱된 TrueType 폰트 데이터) vs createStandalone()(매 요청마다 TTF 파싱)을 비교합니다.

ModeFactory (TTF cached)Standalone (TTF parse)
Single Thread364243
4 Workers1,327871

Factory 처리량 우위: 1.5x (싱글 스레드). 캐싱된 TrueType 폰트 데이터가 요청별 .ttf 파싱 오버헤드를 제거합니다. 4개 워커 사용 시 팩토리는 1,327 docs/sec를 달성합니다 — 독립형 대비 52.4% 향상입니다.

RoadRunner HTTP 처리량

DocumentFactory 워커 패턴을 사용한 RoadRunner 기반 실제 HTTP 서버 벤치마크입니다. ab (Apache Bench)로 측정되었습니다.

ConfigDocs/secMean Latency (ms)p50 (ms)p99 (ms)Failed
1 worker / 1 concurrent1,3200.76110
4 workers / 4 concurrent4,8120.83110

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, response write)의 비용을 반영합니다. 이 오버헤드에도 불구하고, 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/MultiCell API를 사용합니다. DomPDF와 mPDF는 동등한 HTML 마크업(기본 인터페이스)을 사용합니다.
  • TrueType 폰트 테스트는 실제 폰트 파싱 비용을 노출하기 위해 DejaVu Sans (~700 KB .ttf)를 사용합니다. Helvetica (Base14) 테스트는 오버헤드 제로 베이스라인을 보여줍니다.
  • Artisan (Chrome)은 픽셀 퍼펙트 CSS3 렌더링을 위해 CDP를 통한 headless Chromium을 사용합니다(CSP를 통해 JavaScript 비활성화).
  • Artisan 단계별은 Chrome 렌더링을 분해합니다: Phase 1 (Chrome CDP printToPDF) vs Phase 2 (PdfReader + PageImporter + embed).
  • 워커 라이프사이클DocumentFactory(공유 FontRegistry + lock, ImageRegistry)와 createStandalone()(호출마다 새 레지스트리)를 비교합니다.
  • 워커 라이프사이클 TTFDocumentFactory의 핵심 가치를 보여줍니다: 수천 건의 워커 요청에 걸쳐 캐싱된 TrueType 폰트 데이터.
  • RoadRunner HTTProadrunner-server/roadrunnerDocumentFactory 워커 패턴으로 사용하며, 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)는 제외됩니다 — 수천 건의 요청에 걸쳐 분산되는 일회성 비용입니다.
  • 지연 시간 vs 처리량 차이: 단일 실행 지연 시간 측정에는 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 버그 유형입니다.

벤치마크 재현

벤치마크 스위트는 저장소에 포함되어 있습니다. 이 결과를 재현하려면:

bash
cd benchmark
docker compose up --build

결과는 실행 종료 시 stdout에 출력됩니다. Docker 설정은 호스트 OS에 관계없이 동일한 환경을 보장합니다.

추가 자료

LGPL-3.0-or-later 라이선스로 배포됩니다.