-
-
Notifications
You must be signed in to change notification settings - Fork 0
Benchmarks
The measured baselines for cudec's LZ4 decoder. A number without its
methodology is not a claim — every figure here carries the conditions it was
measured under (GPU, driver, CUDA version, corpus, chunk-size distribution, run
count), and the benchmark harness (bench/bench_lz4) emits that block inline so
a bare number cannot be produced by construction. The raw record, with the full
harness reports, is
docs/BENCHMARKS.md;
the narrative on how to read and reproduce these numbers is
docs/BENCHMARK-METHODOLOGY.md.
This page is a summary of both.
- These are single-machine numbers from the development hardware, not a
cross-vendor sweep. GPU: NVIDIA GeForce RTX 3080 (sm_86, 10 GB GDDR6X, ~760
GB/s output-bandwidth ceiling), driver 12.6, CUDA runtime 12.6, inside the
digest-pinned dev container (
nvidia/cuda:12.6.2-devel-ubuntu24.04). Host CPU for the reference: AMD Ryzen 9 5950X. GPU timing jitters ~1–2% run to run. - The CPU figure is the reference the GPU is measured against: the
single-threaded liblz4 1.10.0
LZ4_decompress_safe, not a parallel CPU decode. The comparison is honest about that. - The device-resident GPU rows exclude host↔device transfer (the data is already on the GPU, CUDA-event timed). The streaming rows include the transfer and per-wave submission — a different, honest metric, not a regression of the device-resident number.
- No cudec-vs-nvCOMP head-to-head is published here; the reasoning (and how a third party can run one under their own license) is in the methodology document. cudec's own numbers and the CPU comparison are what is reported.
The average-case throughput on the Silesia corpus (3239 chunks, ~212 MB original, median chunk 64 KiB), recorded 2026-07-17:
| Path | Throughput (p50) | Relative |
|---|---|---|
| CPU oracle (liblz4, single thread) | ~3.41 GB/s | 1× (reference) |
| cudec GPU decode (device-resident) | ~18.1 GB/s | ~5.3× CPU |
| cudec GPU parse-only ceiling (copies elided) | ~34.6 GB/s | ~10× CPU |
Full harness report for the GPU decode:
## bench_lz4 report
- decoder: CPU oracle, LZ4_decompress_safe (liblz4 1.10.0), single thread
- host CPU: AMD Ryzen 9 5950X 16-Core Processor
- CUDA device: NVIDIA GeForce RTX 3080 (sm_86), driver 12.6, runtime 12.6
- corpus: dickens+mozilla+mr+nci+ooffice+osdb+reymont+samba+sao+webster+x-ray+xml, 3239 chunks, 211.94 MB original, 102.44 MB compressed (ratio 0.483), compressed in-harness via LZ4_compress_default
- chunk sizes: min 8066 / median 65536 / max 65536 bytes
- method: 3 warmup + 30 measured runs; CPU wall clock per whole-batch decode; GPU device-resident, CUDA-event timed; output byte-verified before timing
- CPU decode throughput (liblz4 baseline): p50 3.41 GB/s
- GPU decode (cudec, device-resident): p50 11.7 ms, 18.1 GB/s (~5.3x the CPU baseline)
- GPU parse-only ceiling (copies elided): p50 6.1 ms, 34.6 GB/s
The parse-only ceiling matters beyond a curiosity: the decoder is parse-bound,
not bandwidth-bound, and that single number settles the kernel-decomposition
question (a two-phase scan-then-copy design shares the identical serial parse,
so it cannot exceed the same ceiling). Two measured optimization passes
(copy/parse micro-ops, and a higher-occupancy register-reduction lever) were
both rejected by measurement and shipped no code — the detail is in
MASTERPLAN §9 and
the BENCHMARKS record. Reproduce with bench_lz4 --gpu.
A security-posture number, not an average. The worst input for this kernel is a
valid LZ4 block of back-to-back minimum matches (match length 4, offset 1) — the
maximum sequence density a valid block can carry, one parsed sequence per 4
decoded bytes. A standard compressor never emits it (it would extend an offset-1
run into one long match), so the harness constructs it directly (--worst4b)
and the liblz4 oracle validates it before any timing. Recorded on the same
hardware, 3200 identical 64 KB chunks (~210 MB):
| Path | Silesia average | Worst-4Bmatch | Degradation |
|---|---|---|---|
| CPU oracle (liblz4, single thread) | ~3.41 GB/s | ~1.49 GB/s | ~2.3× |
| cudec GPU decode (device-resident) | ~18.1 GB/s | ~8.1 GB/s | ~2.2× |
| cudec GPU parse-only ceiling | ~34.6 GB/s | ~15.3 GB/s | ~2.3× |
The degradation is linear and bounded — every path slows by the same ~2.2–2.3×
factor, which is exactly the sequence-density ratio, with no super-linear
blow-up and no size amplification (each chunk decodes to at most its destination
capacity; the block barely compresses). The GPU advantage holds under the worst
input: ~8.1 GB/s is still ~5.4× the CPU worst case. Reproduce with
bench_lz4 --worst4b --gpu; the construction is oracle-validated in-harness and
locked against rot by a ctest self-check on the GPU-less runner.
The streaming decoder takes host-resident compressed chunks, stages them to the GPU, decodes, and (for host output) reads the result back. Its wall is clocked around the whole synchronous call, so it includes the transfers and the per-wave submission cost — a different metric from the device-resident kernel number above:
| Path (reusable context, steady state) | Throughput (p50) |
|---|---|
| Device output (stays in VRAM) | ~0.92 GB/s |
| Host output (synchronous readback) | ~0.58 GB/s |
These are dominated not by decode (~12 ms device-resident for the whole batch)
or the compressed H2D copy (~4 ms) but by per-wave serial submission: the
batch is submitted in ~51 waves on this WSL2/WDDM setup, where each submission
flush costs milliseconds. Raising the wave granularity so the path submits once
is the open lever, tracked as
issue #33. The reusable context
(cudec_stream_ctx_create / cudec_lz4_decompress_stream_ctx /
cudec_stream_ctx_destroy) exists so the staging allocation — measured at only
~8 ms — is paid once rather than per call; prefer it over the one-shot streaming
entry point for repeated decodes. Reproduce with bench_lz4 --gpu --gpu-stream-ctx.
The corpora are fetched hash-pinned via
bench/get-corpora.sh
(never committed). Build the CUDA target in the pinned container (see
Home for the exact docker run line, with --gpus all for the device
path), then run bench/bench_lz4 with the corpus paths and the --gpu flag.
Every run reprints its own methodology block; that block, not the throughput
line alone, is the citable result.
- docs/BENCHMARKS.md — the raw baseline record with every harness report in full.
- docs/BENCHMARK-METHODOLOGY.md — how the numbers are read, reproduced, and positioned.
- MASTERPLAN §9 — the kernel design and the measured perf-pass outcomes behind these figures.
- Home · C API reference