Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d77a0bd
perf(encode): reuse rollback entropy snapshot buffers per block
polaz Jun 10, 2026
1287a27
perf(encode): feed slice inputs to owned block loop without zero-fill
polaz Jun 10, 2026
1b5279c
perf(encode): epoch-bias Fast table reset for dict-attach frames
polaz Jun 10, 2026
f2cd65b
perf(encode): fold optimal-plan profile dispatch on strategy consts
polaz Jun 10, 2026
53e8305
test(bench): decode_loop_dict profiling example + dict payload dump
polaz Jun 10, 2026
01aa801
test(bench): dump scenario input bytes alongside the trained dict
polaz Jun 10, 2026
737909e
perf(encode): donor pre-build incompressibility gate for literals
polaz Jun 10, 2026
4b58210
docs(encode): document why compress() source is not restored on unwind
polaz Jun 10, 2026
6233d0f
perf(encode): run the lazy band (L6-12) on the row match finder
polaz Jun 10, 2026
773b09a
perf(encode): iterate row tag-mask hits instead of scanning every slot
polaz Jun 10, 2026
6992e67
fix(bench): non-fatal dump writes; validate decode length in decode_l…
polaz Jun 10, 2026
bfd4950
perf(encode): 4-byte gate before repcode common_prefix_len
polaz Jun 10, 2026
6b5773c
perf(encode): prefetch the upcoming row in the lazy scan loop
polaz Jun 10, 2026
ba30e78
perf(encode): row hash ring cache feeding probe and post-miss insert
polaz Jun 10, 2026
d83ba61
fix(encode): keep btlazy2 off the Row finder; donor hashLog cap for r…
polaz Jun 10, 2026
bda8010
Revert "perf(encode): row hash ring cache feeding probe and post-miss…
polaz Jun 10, 2026
fc1cff3
Revert "perf(encode): prefetch the upcoming row in the lazy scan loop"
polaz Jun 10, 2026
4874f0b
fix(encode): let donor row hash widths reach the backend; btlazy2 par…
polaz Jun 10, 2026
ee9bc09
fix(encode): key row snapshots on the applied hash width, keep 20-bit…
polaz Jun 10, 2026
985ca24
fix(bench): checksum parity for the FFI dictionary arms
polaz Jun 10, 2026
d044bc1
fix(encode)!: default content checksum off (upstream library parity)
polaz Jun 10, 2026
5bf5a03
docs(encode): align checksum-default docs and bench config ordering
polaz Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,12 @@ fn compress_stream<R: Read, W: Write>(
CompressionLevel::from_level(level)
};
let mut encoder = structured_zstd::encoding::StreamingEncoder::new(writer, compression_level);
// The reference `zstd` COMMAND defaults the content checksum ON (unlike
// the library API, whose default is off and which our encoder mirrors) —
// set it explicitly so CLI output matches `zstd <file>` byte layout.
encoder
.set_content_checksum(true)
.wrap_err("failed to enable content checksum")?;
// Long-distance matching (`--long`) is a per-knob override applied via the
// compression-parameters API; skip it for `--store` (raw frames don't match).
if long && !store {
Expand Down
75 changes: 58 additions & 17 deletions zstd/benches/compare_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,42 @@ use support::{

static BENCHMARK_SCENARIOS: OnceLock<Vec<Scenario>> = OnceLock::new();

/// Enable `ZSTD_c_enableLongDistanceMatching` on an FFI bulk compressor for
/// the LDM variants; a no-op for the plain numeric levels.
fn apply_ffi_ldm(compressor: &mut zstd::bulk::Compressor<'_>, level: &LevelConfig) {
/// Apply the matrix variant's options to an FFI bulk compressor: LDM for
/// the `*_ldm*` variants and the checksum flag (parity with both
/// `ffi_encode_to_vec` and the Rust encoder's default).
fn configure_ffi_bulk_compressor(compressor: &mut zstd::bulk::Compressor<'_>, level: &LevelConfig) {
if level.ldm {
compressor
.set_parameter(zstd::zstd_safe::CParameter::EnableLongDistanceMatching(
true,
))
.expect("FFI bulk compressor accepts EnableLongDistanceMatching");
}
// Checksum parity with the no-dict groups: `ffi_encode_to_vec` sets
// `ZSTD_c_checksumFlag` under the `hash` feature, but the bulk
// compressors used by the dictionary arms default it OFF — leaving the
// Rust side (content checksum on by default) paying the XXH64 pass the
// FFI side skipped (~6% of frame time on small dict frames).
if cfg!(feature = "hash") {
compressor
.set_parameter(zstd::zstd_safe::CParameter::ChecksumFlag(true))
.expect("FFI bulk compressor accepts ChecksumFlag");
}
}

/// Build the matching Rust-encoder bytes for a matrix variant. The plain
/// numeric levels keep the historical `compress_slice_to_vec` path so their
/// output stays byte-for-byte identical to pre-#362 runs; the LDM variants
/// route through `compress_with_parameters` with
/// `enable_long_distance_matching(true)` on the variant's base level.
/// Build the matching Rust-encoder bytes for a matrix variant. The bench
/// matrix measures the FULL feature gate on both sides: the content
/// checksum is enabled explicitly here (the encoder's default mirrors the
/// upstream library default, OFF) just as `ffi_encode_to_vec` and
/// `configure_ffi_bulk_compressor` enable `ZSTD_c_checksumFlag`, all under
/// the same `hash` feature.
fn rust_encode_to_vec(input: &[u8], level: &LevelConfig) -> Vec<u8> {
match ldm_parameters(level) {
Some(params) => structured_zstd::encoding::compress_with_parameters(input, &params),
None => structured_zstd::encoding::compress_slice_to_vec(input, level.rust_level),
let mut enc: FrameCompressor = FrameCompressor::new(level.rust_level);
if let Some(params) = ldm_parameters(level) {
enc.set_parameters(&params);
}
enc.set_content_checksum(cfg!(feature = "hash"));
enc.compress_independent_frame(input)
}

/// FFI encode helper used by criterion's timing loop. Uses
Expand Down Expand Up @@ -571,8 +585,19 @@ fn bench_dictionary(c: &mut Criterion) {
// compress-dict bench/REPORT use. Gated on an env var so normal
// bench runs are unaffected.
if let Ok(dir) = std::env::var("STRUCTURED_ZSTD_DUMP_DICT_DIR") {
// Diagnostic artifacts are non-critical: warn and keep benching on
// I/O failure instead of aborting the whole run.
let path = format!("{dir}/{}.dict", scenario.id);
std::fs::write(&path, &ffi_dictionary).expect("dump dict");
if let Err(err) = std::fs::write(&path, &ffi_dictionary) {
eprintln!("BENCH_WARN failed to dump dict {path}: {err}");
}
// Scenario input bytes too, so standalone profiling binaries
// (`encode_loop_dict` / `decode_loop_dict`) can replay the
// exact (input, dict) pair this scenario benches.
let path = format!("{dir}/{}.bin", scenario.id);
if let Err(err) = std::fs::write(&path, scenario.bytes.as_slice()) {
eprintln!("BENCH_WARN failed to dump scenario bytes {path}: {err}");
}
}

if emit_reports {
Expand Down Expand Up @@ -653,19 +678,31 @@ fn bench_dictionary(c: &mut Criterion) {
// the plain compress/decompress groups, not the dictionary group.
// Everything else runs here: the numeric levels (unchanged
// behaviour) and the `*_ldm_dict` variants (`dict = true`), the
// latter with LDM enabled on both sides via `apply_ffi_ldm` /
// latter with LDM enabled on both sides via `configure_ffi_bulk_compressor` /
// `ldm_parameters` below.
if level.ldm && !level.dict {
continue;
}
let mut no_dict = zstd::bulk::Compressor::new(level.ffi_level).unwrap();
apply_ffi_ldm(&mut no_dict, &level);
configure_ffi_bulk_compressor(&mut no_dict, &level);
let mut with_dict =
zstd::bulk::Compressor::with_dictionary(level.ffi_level, &ffi_dictionary).unwrap();
apply_ffi_ldm(&mut with_dict, &level);
configure_ffi_bulk_compressor(&mut with_dict, &level);
let no_dict_bytes = no_dict.compress(&scenario.bytes).unwrap();
let with_dict_bytes = with_dict.compress(&scenario.bytes).unwrap();

// Diagnostic: dump the FFI dict-encoded payload next to the
// trained dict (same env gate) so standalone profiling binaries
// (`decode_loop_dict`) can decode the EXACT bytes the
// `decompress-dict/...` bench arm measures.
if let Ok(dir) = std::env::var("STRUCTURED_ZSTD_DUMP_DICT_DIR") {
// Non-critical diagnostic: warn, do not abort the bench.
let path = format!("{dir}/{}.{}.zst", scenario.id, level.name);
if let Err(err) = std::fs::write(&path, &with_dict_bytes) {
eprintln!("BENCH_WARN failed to dump dict payload {path}: {err}");
}
}

// Rust dict-compressed output size, for the compress-dict
// compression-ratio report (rust vs FFI). Only computable when the
// dictionary parsed into a Rust handle; mirrors the gate the
Expand All @@ -679,6 +716,8 @@ fn bench_dictionary(c: &mut Criterion) {
if let Some(params) = ldm_parameters(&level) {
warmup_compressor.set_parameters(&params);
}
// Full feature gate: checksum on, matching the FFI arms.
warmup_compressor.set_content_checksum(cfg!(feature = "hash"));
warmup_compressor
.set_dictionary_from_bytes(&ffi_dictionary)
.expect("dictionary should attach");
Expand Down Expand Up @@ -722,7 +761,7 @@ fn bench_dictionary(c: &mut Criterion) {
group.bench_function("c_ffi_without_dict", |b| {
b.iter(|| {
let mut compressor = zstd::bulk::Compressor::new(level.ffi_level).unwrap();
apply_ffi_ldm(&mut compressor, &level);
configure_ffi_bulk_compressor(&mut compressor, &level);
black_box(compressor.compress(&scenario.bytes).unwrap())
})
});
Expand All @@ -744,7 +783,7 @@ fn bench_dictionary(c: &mut Criterion) {
let mut compressor =
zstd::bulk::Compressor::with_dictionary(level.ffi_level, &ffi_dictionary)
.unwrap();
apply_ffi_ldm(&mut compressor, &level);
configure_ffi_bulk_compressor(&mut compressor, &level);
b.iter(|| black_box(compressor.compress(&scenario.bytes).unwrap()))
});

Expand All @@ -769,6 +808,8 @@ fn bench_dictionary(c: &mut Criterion) {
if let Some(params) = ldm_parameters(&level) {
compressor.set_parameters(&params);
}
// Full feature gate: checksum on, matching the FFI arms.
compressor.set_content_checksum(cfg!(feature = "hash"));
compressor
.set_encoder_dictionary(
EncoderDictionary::from_bytes(&ffi_dictionary)
Expand Down
23 changes: 15 additions & 8 deletions zstd/benches/compare_ffi_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,14 @@ fn main() {

let (rust_compressed, rust_peak) = measure_peak(|| {
let mut compressor: FrameCompressor = FrameCompressor::new(level.rust_level);
// Params before the checksum flag — same ordering as
// `rust_encode_to_vec` so every bench arm configures the
// compressor identically.
if let Some(params) = &ldm_params {
compressor.set_parameters(params);
}
// Full feature gate: checksum on, matching the FFI arm.
compressor.set_content_checksum(cfg!(feature = "hash"));
compressor
.set_dictionary_from_bytes(&dict)
.expect("dictionary should attach");
Expand Down Expand Up @@ -556,15 +561,17 @@ fn main() {
continue;
}

// Compress (no dictionary; LDM wired on both sides when set)
let (rust_compressed, rust_peak) = measure_peak(|| match &ldm_params {
Some(params) => {
structured_zstd::encoding::compress_with_parameters(&scenario.bytes[..], params)
// Compress (no dictionary; LDM wired on both sides when set).
// Full feature gate: checksum on, matching the FFI arm.
let (rust_compressed, rust_peak) = measure_peak(|| {
let mut compressor: FrameCompressor = FrameCompressor::new(level.rust_level);
// Params before the checksum flag — same ordering as
// `rust_encode_to_vec`.
if let Some(params) = &ldm_params {
compressor.set_parameters(params);
}
None => structured_zstd::encoding::compress_slice_to_vec(
&scenario.bytes[..],
level.rust_level,
),
compressor.set_content_checksum(cfg!(feature = "hash"));
compressor.compress_independent_frame(&scenario.bytes[..])
});
let (ffi_compressed, ffi_peak) =
measure_peak(|| ffi_encode(&scenario.bytes[..], level.ffi_level, level.ldm, None));
Expand Down
60 changes: 60 additions & 0 deletions zstd/examples/decode_loop_dict.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Standalone decode-loop binary for CLEAN perf-record profiles of the
//! dictionary DECODE hot path. Mirrors the `decompress-dict/...` bench arm
//! exactly: parse the dictionary into a [`DictionaryHandle`] ONCE, build one
//! reused [`FrameDecoder`], then loop `decode_all_with_dict_handle` over a
//! fixed `.zst` payload into a preallocated output buffer — no criterion, no
//! FFI, no training; samples land purely on the per-frame decode path.
//!
//! The payload should be the FFI dict-encoded frame the bench measures; dump
//! both files from a bench run via
//! `STRUCTURED_ZSTD_DUMP_DICT_DIR=<dir> cargo bench --bench compare_ffi ...`
//! (`<scenario>.dict` + `<scenario>.<level>.zst`).
//!
//! Build: `cargo build --profile flamegraph -p structured-zstd
//! --example decode_loop_dict`
//! Run: `decode_loop_dict <iters> <payload.zst> <dict> <expected_len>`

use std::env;

use structured_zstd::decoding::{DictionaryHandle, FrameDecoder};

fn main() {
let args: Vec<String> = env::args().collect();
let iters: u32 = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(500_000);
let payload_path = args
.get(2)
.map(String::as_str)
.unwrap_or("/tmp/szstd-dicts/small-4k-log-lines.level_2_fast.zst");
let dict_path = args
.get(3)
.map(String::as_str)
.unwrap_or("/tmp/szstd-dicts/small-4k-log-lines.dict");
let expected_len: usize = args.get(4).and_then(|s| s.parse().ok()).unwrap_or(4096);

let payload = std::fs::read(payload_path).expect("read payload file");
let dict = std::fs::read(dict_path).expect("read dict file");
let handle = DictionaryHandle::decode_dict(dict.as_slice()).expect("parse dictionary");

let mut decoder = FrameDecoder::new();
let mut output = vec![0u8; expected_len];
let mut sink: usize = 0;
for _ in 0..iters {
let n = decoder
.decode_all_with_dict_handle(payload.as_slice(), output.as_mut_slice(), &handle)
.expect("dict decode should succeed");
// A short decode means the (payload, dict, expected_len) triple is
// mismatched — fail loudly instead of profiling the wrong workload.
assert_eq!(n, expected_len, "decoded length mismatch");
sink = sink.wrapping_add(n);
core::hint::black_box(&output[..n]);
}

eprintln!(
"decoded {} bytes x {} iters (payload {} bytes, dict {} bytes); out-sum={}",
expected_len,
iters,
payload.len(),
dict.len(),
sink
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
27 changes: 27 additions & 0 deletions zstd/examples/donor_cparams_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Print donor cParams for levels 1..=22 at a given source size.
//! Companion to `donor_cparams_check` for sweeping a whole level band.
//!
//! Run: `cargo run --release -p structured-zstd --example donor_cparams_range
//! --features dict_builder -- [src_size]` (0 / omitted = unbounded).
use zstd::zstd_safe::zstd_sys;

fn main() {
let src_size: u64 = std::env::args()
.nth(1)
.and_then(|s| s.parse().ok())
.unwrap_or(0);
for level in 1..=22i32 {
// SAFETY: standard libzstd query.
let cp = unsafe { zstd_sys::ZSTD_getCParams(level, src_size, 0) };
println!(
"L{level}: wlog={} clog={} hlog={} slog={} mml={} tlen={} strat={}",
cp.windowLog,
cp.chainLog,
cp.hashLog,
cp.searchLog,
cp.minMatch,
cp.targetLength,
cp.strategy as u32
);
}
}
7 changes: 7 additions & 0 deletions zstd/src/decoding/frame_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3022,6 +3022,7 @@ mod tests {
// into the persistent scratch's hasher.
let payload: Vec<u8> = (0..8192u32).map(|i| (i & 0xFF) as u8).collect();
let mut compressor = FrameCompressor::new(CompressionLevel::Default);
compressor.set_content_checksum(true);
compressor.set_source(payload.as_slice());
let mut compressed = Vec::new();
compressor.set_drain(&mut compressed);
Expand Down Expand Up @@ -3058,6 +3059,7 @@ mod tests {
use crate::decoding::ContentChecksum;
let payload: Vec<u8> = (0..8192u32).map(|i| (i & 0xFF) as u8).collect();
let mut compressor = FrameCompressor::new(CompressionLevel::Default);
compressor.set_content_checksum(true);
compressor.set_source(payload.as_slice());
let mut compressed = Vec::new();
compressor.set_drain(&mut compressed);
Expand All @@ -3080,6 +3082,7 @@ mod tests {
use crate::decoding::errors::FrameDecoderError;
let payload: Vec<u8> = (0..8192u32).map(|i| (i & 0xFF) as u8).collect();
let mut compressor = FrameCompressor::new(CompressionLevel::Default);
compressor.set_content_checksum(true);
compressor.set_source(payload.as_slice());
let mut compressed = Vec::new();
compressor.set_drain(&mut compressed);
Expand Down Expand Up @@ -3114,6 +3117,7 @@ mod tests {
use crate::decoding::errors::FrameDecoderError;
let payload: Vec<u8> = (0..8192u32).map(|i| (i & 0xFF) as u8).collect();
let mut compressor = FrameCompressor::new(CompressionLevel::Default);
compressor.set_content_checksum(true);
compressor.set_source(payload.as_slice());
let mut compressed = Vec::new();
compressor.set_drain(&mut compressed);
Expand Down Expand Up @@ -3150,6 +3154,7 @@ mod tests {
// the buffered tail (it used to early-return Ok((4,0)) and lose it).
let payload: Vec<u8> = (0..8192u32).map(|i| (i & 0xFF) as u8).collect();
let mut compressor = FrameCompressor::new(CompressionLevel::Default);
compressor.set_content_checksum(true);
compressor.set_source(payload.as_slice());
let mut compressed = Vec::new();
compressor.set_drain(&mut compressed);
Expand Down Expand Up @@ -3179,6 +3184,7 @@ mod tests {
use crate::decoding::ContentChecksum;
let payload: Vec<u8> = (0..8192u32).map(|i| (i & 0xFF) as u8).collect();
let mut compressor = FrameCompressor::new(CompressionLevel::Default);
compressor.set_content_checksum(true);
compressor.set_source(payload.as_slice());
let mut compressed = Vec::new();
compressor.set_drain(&mut compressed);
Expand All @@ -3204,6 +3210,7 @@ mod tests {

let mut with = Vec::new();
let mut c_with = FrameCompressor::new(CompressionLevel::Default);
c_with.set_content_checksum(true);
c_with.set_source(payload.as_slice());
c_with.set_drain(&mut with);
c_with.compress();
Expand Down
3 changes: 3 additions & 0 deletions zstd/src/decoding/streaming_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ mod tests {

let payload: Vec<u8> = (0..8192u32).map(|i| (i & 0xFF) as u8).collect();
let mut compressor = FrameCompressor::new(CompressionLevel::Default);
// Checksum is the subject under test; the encoder default is off
// (upstream library parity).
compressor.set_content_checksum(true);
compressor.set_source(payload.as_slice());
let mut compressed = Vec::new();
compressor.set_drain(&mut compressed);
Expand Down
Loading
Loading