Benchmarks
Takeaways
Section titled “Takeaways”From the run below, on x86-64 Linux with Node.js 24:
- zstd is faster than gzip and brotli in comprs, for compression and decompression alike, with a better ratio than gzip. LZ4 compresses faster still on four of the six inputs, at a lower ratio, but decompresses faster than zstd only for 150 bytes of text and 84 KB of JSON. Against the zstd of
node:zlibat the same level, comprs compresses 1.1–2.7 times and decompresses 1.4–3.6 times as fast, to the same size. - gzip decompression is 3.5–16 times as fast as pako and 1.0–2.0 times as fast as
node:zlib; fflate is faster on incompressible data and on 150 bytes. deflate (zlib) decompression is 1.5–5 times as fast as pako, and 0.8–1.3 times as fast asnode:zlib. - gzip and deflate compression of 84 KB of JSON is 1.5 times as fast as
node:zliband 4.2–4.7 times as fast as pako, at the same ratio within 1%. comprs compresses with zlib-rs, which follows the algorithms of zlib-ng: on extremely repetitive input, such as one sentence repeated, it trades ratio for speed (82:1 against 122:1 for zlib), so the speeds on those inputs are not like for like. For 150 bytes,node:zlibis faster. - brotli comes from libbrotli, the C library of
node:zlib: it compresses to the same size at about the same speed, and decompresses up to 1.5 times as fast. - Node.js streams of comprs are 3.7–7.9 times as fast as those of
node:zlibfor 1 MB in 16 KB chunks. - These numbers are from the native addon; the WebAssembly build was not measured.
Tables
Section titled “Tables”Measured on 2026-10-25 with comprs 2.3.0 (fb6a968), Node.js v24.21.0, pako 3.0.2 and fflate 0.8.3, on AMD EPYC 7763 64-Core Processor × 4 (linux/x64), in this run of the Bench Report workflow, on a GitHub-hosted runner (ubuntu24, image 20261023.334.3). Settings, the same for every library in a table: gzip level 6 · deflate level 6 · brotli quality 6, lgwin 22 · brotli quality 11, lgwin 22 · zstd level 3 · streams: gzip level 6, zstd level 3 · cross-algorithm: each format’s default settings (lz4 has no levels). Speeds are MB/s of uncompressed data (1 MB = 1,000,000 bytes), from the median time of a call. Every library decompresses the output of comprs, so that only the decoders differ, except in the cross-algorithm table, where each format decompresses its own. The compression ratio is the input size divided by the output size.
Cross-algorithm comparison: zstd vs gzip vs brotli vs lz4 (comprs only, default settings)
Compression (MB/s of uncompressed data, higher is better)
| Input | zstd | gzip | brotli | lz4 |
|---|---|---|---|---|
| text 150B | 56.5 | 13.2 | 15.5 | 85.5 |
| JSON 84KB | 583 | 233 | 76.9 | 1,122 |
| text 45KB | 3,535 | 1,715 | 1,602 | 3,194 |
| random 10KB | 932 | 80.8 | 108 | 1,672 |
| random 1MB | 3,811 | 49.9 | 381 | 2,891 |
| patterned 1MB | 6,274 | 2,866 | 1,331 | 3,703 |
Decompression (MB/s of uncompressed data, higher is better)
| Input | zstd | gzip | brotli | lz4 |
|---|---|---|---|---|
| text 150B | 103 | 59.7 | 46.6 | 111 |
| JSON 84KB | 1,757 | 1,395 | 889 | 2,374 |
| text 45KB | 8,026 | 2,992 | 1,034 | 1,772 |
| random 10KB | 4,822 | 2,233 | 3,108 | 2,796 |
| random 1MB | 13,711 | 4,000 | 6,864 | 4,386 |
| patterned 1MB | 17,150 | 4,398 | 1,136 | 1,861 |
Compression ratio (input size ÷ output size, higher is better)
| Input | zstd | gzip | brotli | lz4 |
|---|---|---|---|---|
| text 150B | 4.84 | 3.95 | 5.56 | 2.78 |
| JSON 84KB | 9.25 | 7.54 | 10.4 | 4.60 |
| text 45KB | 285 | 81.8 | 286 | 105 |
| random 10KB | 1.00 | 1.00 | 1.00 | 1.00 |
| random 1MB | 1.00 | 1.00 | 1.00 | 1.00 |
| patterned 1MB | 2,755 | 128 | 3,846 | 199 |
gzip, level 6: comprs vs pako vs fflate vs node:zlib
Compression (MB/s of uncompressed data, higher is better)
| Input | comprs | pako | fflate | node:zlib |
|---|---|---|---|---|
| text 150B | 12.4 | 2.16 | 6.98 | 19.7 |
| JSON 84KB | 231 | 49.0 | 36.3 | 152 |
| text 45KB | 1,709 | 84.5 | 83.7 | 294 |
| random 10KB | 81.2 | 32.5 | 36.5 | 97.9 |
| random 1MB | 49.7 | 20.4 | 36.0 | 48.7 |
| patterned 1MB | 2,834 | 77.7 | 83.2 | 291 |
Decompression (MB/s of uncompressed data, higher is better)
| Input | comprs | pako | fflate | node:zlib |
|---|---|---|---|---|
| text 150B | 59.2 | 17.1 | 189 | 59.0 |
| JSON 84KB | 1,385 | 191 | 375 | 943 |
| text 45KB | 3,001 | 242 | 803 | 1,522 |
| random 10KB | 2,184 | 300 | 6,698 | 1,845 |
| random 1MB | 3,987 | 365 | 9,362 | 2,282 |
| patterned 1MB | 4,265 | 265 | 883 | 2,286 |
Compression ratio (input size ÷ output size, higher is better)
| Input | comprs | pako | fflate | node:zlib |
|---|---|---|---|---|
| text 150B | 3.95 | 3.95 | 4.05 | 3.95 |
| JSON 84KB | 7.54 | 7.58 | 8.09 | 7.58 |
| text 45KB | 81.8 | 122 | 122 | 122 |
| random 10KB | 1.00 | 1.00 | 1.00 | 1.00 |
| random 1MB | 1.00 | 1.00 | 1.00 | 1.00 |
| patterned 1MB | 128 | 237 | 237 | 237 |
deflate, level 6: comprs vs pako vs fflate vs node:zlib
Compression (MB/s of uncompressed data, higher is better)
| Input | comprs | pako | fflate | node:zlib |
|---|---|---|---|---|
| text 150B | 13.1 | 2.20 | 7.48 | 20.7 |
| JSON 84KB | 236 | 56.7 | 41.2 | 158 |
| text 45KB | 2,022 | 113 | 107 | 324 |
| random 10KB | 83.7 | 38.2 | 40.6 | 99.2 |
| random 1MB | 50.0 | 22.0 | 40.7 | 49.5 |
| patterned 1MB | 3,708 | 96.2 | 111 | 332 |
Decompression (MB/s of uncompressed data, higher is better)
| Input | comprs | pako | fflate | node:zlib |
|---|---|---|---|---|
| text 150B | 77.6 | 17.4 | 2.19 | 65.1 |
| JSON 84KB | 1,521 | 365 | 259 | 1,194 |
| text 45KB | 3,284 | 608 | 358 | 2,211 |
| random 10KB | 3,007 | 952 | 2,527 | 3,431 |
| random 1MB | 6,015 | 4,064 | 3,256 | 5,018 |
| patterned 1MB | 4,066 | 790 | 664 | 5,096 |
Compression ratio (input size ÷ output size, higher is better)
| Input | comprs | pako | fflate | node:zlib |
|---|---|---|---|---|
| text 150B | 7.50 | 7.50 | 7.89 | 7.50 |
| JSON 84KB | 7.55 | 7.59 | 8.10 | 7.59 |
| text 45KB | 84.5 | 128 | 129 | 128 |
| random 10KB | 1.00 | 1.00 | 1.00 | 1.00 |
| random 1MB | 1.00 | 1.00 | 1.00 | 1.00 |
| patterned 1MB | 128 | 238 | 238 | 238 |
brotli, quality 6, lgwin 22: comprs vs node:zlib
Compression (MB/s of uncompressed data, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| text 150B | 15.4 | 12.4 |
| JSON 84KB | 77.6 | 76.6 |
| text 45KB | 1,576 | 1,462 |
| random 10KB | 108 | 101 |
| random 1MB | 423 | 415 |
| patterned 1MB | 1,335 | 1,328 |
Decompression (MB/s of uncompressed data, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| text 150B | 45.9 | 37.3 |
| JSON 84KB | 908 | 753 |
| text 45KB | 1,041 | 808 |
| random 10KB | 3,081 | 2,743 |
| random 1MB | 6,844 | 4,540 |
| patterned 1MB | 1,131 | 1,124 |
Compression ratio (input size ÷ output size, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| text 150B | 5.56 | 5.56 |
| JSON 84KB | 10.4 | 10.4 |
| text 45KB | 286 | 286 |
| random 10KB | 1.00 | 1.00 |
| random 1MB | 1.00 | 1.00 |
| patterned 1MB | 3,846 | 3,846 |
brotli, quality 11, lgwin 22: comprs vs node:zlib
Compression (MB/s of uncompressed data, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| JSON 84KB | 0.74 | 0.75 |
| patterned 10KB | 13.1 | 13.6 |
Decompression (MB/s of uncompressed data, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| JSON 84KB | 885 | 733 |
| patterned 10KB | 693 | 669 |
Compression ratio (input size ÷ output size, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| JSON 84KB | 13.5 | 13.5 |
| patterned 10KB | 46.1 | 46.1 |
zstd, level 3: comprs vs node:zlib
Compression (MB/s of uncompressed data, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| text 150B | 56.5 | 20.3 |
| JSON 84KB | 596 | 522 |
| text 45KB | 3,519 | 1,916 |
| random 10KB | 917 | 555 |
| random 1MB | 3,825 | 2,046 |
| patterned 1MB | 6,270 | 1,834 |
Decompression (MB/s of uncompressed data, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| text 150B | 103 | 36.5 |
| JSON 84KB | 1,757 | 1,280 |
| text 45KB | 7,917 | 2,952 |
| random 10KB | 4,753 | 2,124 |
| random 1MB | 14,178 | 4,506 |
| patterned 1MB | 17,091 | 4,801 |
Compression ratio (input size ÷ output size, higher is better)
| Input | comprs | node:zlib |
|---|---|---|
| text 150B | 4.84 | 4.84 |
| JSON 84KB | 9.25 | 9.25 |
| text 45KB | 285 | 285 |
| random 10KB | 1.00 | 1.00 |
| random 1MB | 1.00 | 1.00 |
| patterned 1MB | 2,755 | 2,755 |
Node.js streams: comprs vs node:zlib
Transform streams (MB/s of uncompressed data, higher is better)
| Stream | Input | comprs | node:zlib |
|---|---|---|---|
| gzip compress, level 6 | patterned 1MB in 16KB chunks | 1,175 | 166 |
| gzip decompress, level 6 | patterned 1MB in 16KB chunks | 1,710 | 329 |
| gzip round-trip, level 6 | patterned 1MB in 16KB chunks | 762 | 108 |
| zstd compress, level 3 | patterned 1MB in 16KB chunks | 1,646 | 388 |
| zstd decompress, level 3 | patterned 1MB in 16KB chunks | 2,925 | 425 |
| zstd round-trip, level 3 | patterned 1MB in 16KB chunks | 837 | 289 |
How they are measured
Section titled “How they are measured”The Bench Report workflow runs scripts/bench-report.mjs on a GitHub-hosted runner, with every library at the same settings, and writes the tables and the charts from that one run, with the versions, the machine and the settings above the tables. Run the benchmarks yourself with pnpm run bench in a clone of the repository; numbers vary by machine and data. CodSpeed tracks the Rust benchmarks on the pull requests that change Rust code, to catch performance regressions.