Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy-zoomies

znippy-zoomies

Single-purpose parallel Rust primitives built on one all-core engine, not rayon's fork-join model.

Decompressor benchmarks

Each of our parallel decoders against the standard tools for its own format, grouped by format — every one of ours vs every one of theirs, in two modes. One deterministic 1 GB corpus, min-of-3. Columns are hand-picked (MB/s · cores · wall s · peak RSS). A 🏆 marks a decoder that beat a long-reigning record — see records.toml.

Multi core variants — all cores

gzip

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-gz 5760 10.24 0.18 416.60
pigz 589.32 1.27 1.74 2.50

bzip2

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-bz2 744.39 11.62 1.38 80.60
lbzip2 759.55 11.76 1.35 90.60

zip

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-zip 2806 3.10 0.36 119.50
unzip 294.17 0.99 3.48 3.10

jar

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-jar 2814 3.19 0.36 119.20
jar 372.87 0.99 2.75 1.80

Single core variants — one core each

gzip

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-gz_st 655.62 0.99 1.56 239.80
gzip 684.25 0.98 1.50 1.90
pigz 589.32 1.27 1.74 2.50

bzip2

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-bz2_st 129.29 1.01 7.92 75.60
bzip2 60.02 1 17.06 5.10
lbzip2_st 70.64 1 14.50 7.30
pbzip2 60.29 1 16.98 41.80

zip

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-zip_st 1354 0.98 0.76 12.10
unzip 294.17 0.99 3.48 3.10

jar

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-jar_st 1356 0.98 0.76 12.20
jar 372.87 0.99 2.75 1.80

cores_busy = avg CPU cores busy during decode (12 = full saturation). znippy-* run all cores; _st rows are pinned to one worker. 🏆 = beat a long-reigning record (records.toml).

Latest run — 32 cores, 2026-07-23: the harness refused to publish it

Not benched. This repo contributed no numbers to the latest run.

nornir-bench entrypoint examples/nornir-bench.rs, osm-katana/examples/nornir-bench.rs
criterion [[bench]] targets 15 (Cargo.toml ×5, lgz/Cargo.toml ×1, linflate/Cargo.toml ×2, ljar/Cargo.toml ×2, lzip-parallel/Cargo.toml ×1, osm-katana/Cargo.toml ×4)
host-stamped result none

An entrypoint exists but produced no run — it was not part of the latest pass, or it exited without emitting a BenchRun.

The published tables above are untouched by this run, and that is deliberate: a preview may never overwrite a measured, published table. nornir docs render hides a preview run from every marker that has not opted in with preview=yes, so the blocks below are the only ones it can reach.

Nothing here failed. The decoders did exactly what they were asked and the roundtrip tests passed — but the OSM-XML input was 24 MB against the 1024 MB heavy floor, so the harness declined to emit a publishable BenchRun for the whole run rather than let an under-floor pass become a number somebody quotes. Read every row below as a preview: honest measurements of a workload too small to publish.

gzip — preview

v0.1.20 · PREVIEW · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · corpus 1024 MB · osm-katana input 24 MB · min-of-3 · 32 cores · 2026-07-23

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-gz 12800 13.87 0.08 733.50
pigz 459.19 1.23 2.23 2.10
gzip 538.95 1 1.90 1.70

bzip2 — preview

v0.1.20 · PREVIEW · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · corpus 1024 MB · osm-katana input 24 MB · min-of-3 · 32 cores · 2026-07-23

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-bz2 1164 26.60 0.88 188.90
lbzip2 1296 29.37 0.79 259.90
bzip2 46.11 1 22.21 4.80
pbzip2 46.21 1 22.16 85.60

one core each — preview

v0.1.20 · PREVIEW · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · corpus 1024 MB · osm-katana input 24 MB · min-of-3 · 32 cores · 2026-07-23

workload decompress_mbs cores_busy decompress_s peak_mem_mb
znippy-gz_st 1313 1 0.78 239.30
gzip 538.95 1 1.90 1.70
znippy-bz2_st 56.33 1 18.18 74.40
bzip2 46.11 1 22.21 4.80
lbzip2_st 54.91 1 18.65 6.90
pbzip2 46.21 1 22.16 85.60

OSM convert — preview, and the arm that put the whole run under the floor

v0.1.20 · PREVIEW · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · corpus 1024 MB · osm-katana input 24 MB · min-of-3 · 32 cores · 2026-07-23

workload convert_mbs cores_busy convert_s peak_mem_mb
osm-katana 200 3.17 0.12 301.70
osm-katana_st 72.73 0.97 0.33 262.90
osmium 68.57 1.31 0.35 35.40

No zip / jar rows: those arms did not run in this pass. The host string above is the harness's own — it composes machine from the CPU/RAM/corpus it measured and ignores NORNIR_MACHINE, so this run reads as its Threadripper rather than as oden. To promote any of this to a published table: NORNIR_ZOOMIES_CORPUS_MB=1024 and an osm-katana input ≥ 1024 MB (or NORNIR_OSM_KATANA_CLIP=<region> against a planet file).

Compressor benchmarks

Everything above is decode. ldeflate (published 2026-08-03) is the workspace's one write-side crate — the parallel DEFLATE/zlib engine plus the gzip, ZIP and JAR deflators built on it. It gets its own section rather than a column above, because a compressor cannot be raced the way a decoder can: the bake-off reconstructs one fixed archive, while gzip -6 and this crate emit different bytes, so MB/s alone ranks nothing. The contested number is what the schedule costs against the same crate's serial path over the same objects at the same level — so the baseline row is compress_objects_serial, not gzip.

plan::compress_objects is the only scheduler in the crate. gzip, ZIP and JAR route through it and differ only in framing — Container::wrap puts a header and trailer around a body the planner already produced, and the three containers added no scheduling code at all. The planner picks a unit kind per object by size: Unit::Batch below coalesce_below = 64 KiB (one task drains many, reusing one encoder), Unit::Whole, or Unit::Segment above 4 MiB (cut at Z_FULL_FLUSH, so one big blob cannot serialise a worker). Unit::Batch covers 93.8% of real git objects — measured over 48 917 of them — which is why one Config serves both git and LFS. limits.rs refuses an amplifying request past MAX_PIECES = 262 144 rather than attempting it.

Scaling — the same crate against its own serial path

oden · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · 30 770 real git objects, 715.4 MB, median 986 B · level 6 · min-of-3 · load 40–47 · 2026-08-04

workload mb_per_sec cores_busy speedup_vs_serial speedup ÷ cores held
deflate.repack_git_objects_st 72.5 — 1.00 —
deflate.repack_git_objects_mt1 69.2 0.93 0.95 1.02
deflate.repack_git_objects_mt2 137.1 1.84 1.89 1.03
deflate.repack_git_objects_mt4 258.5 3.55 3.57 1.01
deflate.repack_git_objects_mt8 424.4 5.86 5.86 1.00
deflate.repack_git_objects_mt16 621.1 8.48 8.57 1.01
deflate.repack_git_objects_mt 842.7 12.15 11.63 0.96

Read the last column. Above 8 threads the speedup_vs_serial column measures the box, not the code — oden is shared and this crate takes every core it can get, so a run that asks for 32 threads and is given 12.15 cores returns 11.63×. Across a 7-run batch on 2026-08-03 the 32-thread speedup spanned 10.38×–23.20× (123.5% spread) while the CPU cost of the schedule held to 1.025×–1.054× of serial, and efficiency against cores actually held stayed in a 94.9–97.6% band at every occupancy. An earlier headline of 28.30× / 88% / 2266 MB/s is not refuted — carried forward it predicts 29.2–31.2× at that occupancy — but it was a statement about machine occupancy dressed as one about the crate, it cannot be reproduced on a shared box, and it is retired as a crate claim. Full tables, the negative controls and both falsified hypotheses: benchmarks.md.

Never compress what is already compressed

precompressed.rs gates the entropy coder on hint → extension → magic-byte probe — no entropy estimate, no tunable threshold, at most 16 byte compares. Acting on a store verdict is free because stored_deflate emits RFC 1951 §3.2.4 stored blocks: a length-prefixed memcpy, not flate2 at level 0, which would still build, reset and walk the encoder.

oden · 24 real git packfiles + pack indexes · 113.8 MiB · level 6 · min-of-3 · decided on CPU seconds · load 66 · 2026-08-04

probe cpu-s per GiB wall MB/s ratio
off — deflate everything 22.85 184.8 1.0041
on — store the already-compressed 0.99 724.6 0.9999

→ 23.1× less CPU, 95.7% saved, output +0.41% larger.

Decided on CPU seconds, not wall time: at load 66 the wall column is noise (the same arm read 267.5 / 2075.5 MB/s on a quieter box) while cpu-s per GiB moved under 2%. Quote the saving, not the multiplier — across four runs on 2026-08-03/04 the multiplier wandered 19.4×–23.1× because its denominator is ~1 cpu-s/GiB, while the saving held at 94.8–95.7% and the size penalty came out byte-identical (+0.41%) every time. It independently reproduces znippy's own 92–95% figure. Negative controls, same harness: with the probe given nothing to find (git loose objects) it collapses to 1.03×, and with the probe vetoed by ContentHint::Compressible over the same packfiles to 1.04× at byte-identical output — so the 23× is a property of the probe.

OSM converter

osm-katana (in-repo sub-crate) converts OSM-XML/PBF → GeoParquet all-core on a no-barrier gatling pipeline, head-to-head with the legacy reference tools osmium / osmconvert for the same input. One convert pass, min-of-3. Columns: MB/s, cores busy, wall seconds, peak RSS (MB).

v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13

workload convert_mbs cores_busy convert_s peak_mem_mb
osm-katana 115.40 1.35 0.21 307.70
osmium 126.20 1.31 0.19 36.60
osmconvert 136.70 0.91 0.18 267.20

convert_mbs = input MB / wall; cores_busy = average CPU cores busy (12 = full saturation on this box). osm-katana runs all cores; osmium / osmconvert are the single-stream C++ references, benched only when installed. By default a small synthetic OSM-XML corpus exercises the emit path on any box — the HEADLINE number comes from the off-box planet run (NORNIR_OSM_KATANA_INPUT=<planet.osm> nornir bench run znippy-zoomies). Region clip headline (cut Sweden out of the planet with our tool): add NORNIR_OSM_KATANA_CLIP=10.9,55.3,24.2,69.1.

Staged pipeline

The classic planet chain — each stage eats the previous stage's output file: xml2-pbf (OSM XML/bz2 → PBF) → pbf2-geo (PBF → GeoParquet) → geo2-arrow (GeoParquet → Arrow IPC). NORNIR_OSM_KATANA_CHAIN=1 nornir bench run znippy-zoomies warehouses one row per stage (stage_mbs = that stage's input MB / wall); point NORNIR_OSM_KATANA_INPUT at a planet for the heavy chain. Off by default — it adds a full three-stage pass — so the table lands after that run.

Docs

License

MIT. The stree module retains Ragnar Groot Koerkamp's copyright notice (see LICENSE).

Files

Newest commits