Skip to content

Benchmarks

pylopdf publishes wins and losses together. These measurements are a snapshot of one machine and corpus—not a universal ranking. Use them to choose what to measure in your own workload.

Latest run

2026-07-26 08:18 UTC · Windows 11 · Python 3.14.6 · AMD64
pylopdf 0.11.0 · pymupdf 1.28.0 · pypdf 6.14.2 · pdfplumber 0.11.10
One warm-up plus five measured runs; tables show median milliseconds.

At a glance

Workload What the latest corpus shows
Merge 10 real-world PDFs pylopdf 36.6 ms, pymupdf 131.8 ms, pypdf 426.3 ms
Render first page at 2× pylopdf led on all ten corpus files
Render 12 pages at 2× render_pages() scaled from 317.4 ms (1 worker) to 81.5 ms (8 workers), a 3.89× speedup
Extract all text pylopdf led on four files; pymupdf led on six
Extraction fidelity proxy Similarity ranged from 0.121 to 1.000 depending on reading-order conventions

Text extraction

All pages, milliseconds; lower is faster.

File pylopdf pymupdf pypdf pdfplumber
bill-hr815.pdf 191.6 183.2 689.7 9997.1
bunka-kokugo-series-019-p4.pdf 1.9 0.5 1.0 1.8
f1040.pdf 26.7 66.9 230.3 704.7
mhlw-doc.pdf 18.4 11.5 114.3 263.3
nics-background-checks-2015-11.pdf 16.1 10.8 177.5 524.7
patent-us223898.pdf 33.3 6.3 79.2 493.7
pdf20-simple.pdf 0.3 1.2 1.7 2.4
senate-expenditures.pdf 6.6 7.2 132.2 374.1
usrguide.pdf 163.0 54.5 673.6 2050.7
wdl6812-manuscript.pdf 0.3 0.8 1.3 2.3

Extraction content

This is a proxy, not a correctness score. Text is whitespace-normalized and compared with pymupdf. Lower similarity for forms and OCR layers can reflect a different reading order or whitespace policy even when character counts match.

File pylopdf characters pymupdf characters Similarity
bill-hr815.pdf 300559 300559 1.000
bunka-kokugo-series-019-p4.pdf 0 0 1.000
f1040.pdf 10156 10156 0.683
mhlw-doc.pdf 1264 1251 0.961
nics-background-checks-2015-11.pdf 5650 5650 0.121
patent-us223898.pdf 11218 11218 0.320
pdf20-simple.pdf 11 11 1.000
senate-expenditures.pdf 4516 4516 0.443
usrguide.pdf 55624 55560 0.996
wdl6812-manuscript.pdf 0 0 1.000

Merge

Task pylopdf pymupdf pypdf
Merge all 10 corpus files 36.6 131.8 426.3

Rendering

First page to a 2× PNG, milliseconds; lower is faster.

File pylopdf pymupdf
bill-hr815.pdf 41.1 88.9
bunka-kokugo-series-019-p4.pdf 48.2 110.5
f1040.pdf 49.5 97.1
mhlw-doc.pdf 35.5 71.2
nics-background-checks-2015-11.pdf 54.2 72.6
patent-us223898.pdf 32.3 68.8
pdf20-simple.pdf 8.0 19.9
senate-expenditures.pdf 55.2 56.8
usrguide.pdf 28.3 54.9
wdl6812-manuscript.pdf 42.9 83.3

Parallel rendering

First 12 pages of usrguide.pdf to 2× PNG, milliseconds; lower is faster. The batch preserves input order and uses one immutable document snapshot.

Workers Time Speedup vs 1 worker
1 317.4 1.00×
2 179.6 1.77×
4 99.1 3.20×
8 81.5 3.89×

Actual concurrency is bounded by both the requested worker count and an estimated 512 MB of live rendering memory.

Free-threaded extraction

Two independent copies of bill-hr815.pdf, all-page text extraction on CPython 3.14.6 free-threaded for Windows 11. One warmup plus the median of seven paired runs, alternating which mode runs first:

Mode Workers Time Speedup
Sequential 1 400.8 ms 1.00×
Parallel 2 235.5 ms 1.70×

Both copies produced exactly the same output in every run, and the interpreter reported that the GIL remained disabled after import.

Reproduce it

The corpus lives in tests/assets/real_world; its sources and licenses are recorded alongside the files.

uv sync --all-extras --group bench
uv run python bench/run.py
uv run python tools/pyodide_compat.py --root . --benchmark-only \
  --benchmark-output .tmp/limits-benchmark.json
# With a free-threaded CPython 3.14 interpreter on Windows:
py -3.14t bench/free_threaded.py
# On POSIX:
python3.14t bench/free_threaded.py

The generated source report is committed at bench/results/latest.md. The separately generated free-threaded report is committed at bench/results/free-threaded-latest.md; rerunning bench/run.py cannot overwrite its CPython 3.14t evidence. The native/Pyodide resource-policy baseline is committed separately at bench/results/limits-latest.md. The second command measures bounded open/extract and controlled rejection. CI runs the same cases inside Pyodide and records Wasm linear-memory growth; those timing and memory values are trends, not native/Wasm performance claims. When quoting a number, include the environment and corpus.