forked from KillingSpark/zstd-rs
-
Notifications
You must be signed in to change notification settings - Fork 1
perf(codec): per-strategy block-split levels + per-tier exec-macro seq monolith #354
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
10ce499
perf(decode): share dictionary by handle instead of per-frame copy
polaz cadab08
perf(decode): copy only decode-essential entropy on dict reinit
polaz 1c830ae
test(bench): measure compress-dict steady-state with reused context
polaz 007864f
perf(encode): cache primed dict matcher state (CDict-equivalent)
polaz 32a4ebe
Revert "perf(encode): cache primed dict matcher state (CDict-equivale…
polaz 7f04de0
Reapply "perf(encode): cache primed dict matcher state (CDict-equival…
polaz 3a2a5eb
perf(encode): gate dict prime-snapshot copy to large sources
polaz 677b163
perf(encode): size block-read buffer to source hint, not always 128 KiB
polaz 9bcb13b
refactor(encode): bound block-read sizing without saturating arithmetic
polaz a9b0f38
perf(encode): share dictionary entropy tables by handle, not per-fram…
polaz 4a05b14
perf(encode): cache Fast dict table across attach-path frames
polaz 3e8be02
perf(decode): move decompression-bomb ceiling off the per-match hot path
polaz d415f54
docs(encode): note retire_dictionary_budget saturating_sub is a real …
polaz 7f3caab
fix(encode): key dict prime snapshot on resolved matcher shape
polaz 8c1e350
perf(decode): single-compare sequence bounds check + per-block bomb c…
polaz f2fdb97
perf(encode): donor block-split levels for fast/dfast/greedy/lazy
polaz c719286
test(bench): add C-decoder loop for reference decode profiling
polaz ee46aa3
perf(decode): inline AVX2 exec body into seq monolith via macro
polaz 5a66be1
fix(decode): silence dead_code on inline-exec trait accessors for non…
polaz a0c1ec9
perf(decode): switch-on-length HUF DTable fill (donor HUF_fillDTableX1)
polaz cdb6a4c
Revert "perf(decode): switch-on-length HUF DTable fill (donor HUF_fil…
polaz 3255616
fix(encode): scope fast_attach to Simple backend in prime snapshot key
polaz 9bbdd88
Merge branch 'perf/#349-dict-decode-warm-reuse' into perf/block-split…
polaz a85e6d4
Merge branch 'main' into perf/block-split-fast
polaz 5612adb
perf(decode): fuse match-copy into VBMI2 + BMI2 seq monoliths
polaz fda3ce4
test(codec): harden window test + diagnostic examples
polaz b0c649e
fix(decode): gate shared exec macros on their kernel features
polaz 5473f8f
fix(decode): gate AVX2 boundary tests on std feature
polaz 73b2b95
fix(encode): donor-correct pre-split level for lazy2/btlazy2
polaz f7a57a6
test(codec): cover fast borrowed split path + harden header parse
polaz adfe3af
perf(encode): align lazy band to donor clevels.h, drop oversized pres…
polaz d8ed7d0
perf(encode): config-drive dfast hash sizing from donor clevels.h
polaz 82cf40c
refactor(encode): per-strategy Option configs in LevelParams + L5 don…
polaz 45ae0cc
refactor(encode): name raw-fast-path window cutoff for its purpose
polaz 143ec11
test(encode): pin fast borrowed split decision + raw-fast-path over-cap
polaz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| //! Standalone C-decoder loop for a clean perf-record profile of the | ||
| //! reference decoder, to compare its hot-path breakdown against our | ||
| //! `decode_loop_z000033`. Encodes the in-tree z000033 once at the given | ||
| //! level via FFI, then decodes it N times through a reused `ZSTD_DCtx` | ||
| //! (steady state, no per-iter context alloc). | ||
| //! | ||
| //! Build: cargo build --profile flamegraph -p structured-zstd \ | ||
| //! --example c_decode_loop_z000033 --features dict_builder | ||
| //! Run: perf record -F 999 -g --call-graph dwarf,16384 -- \ | ||
| //! target/flamegraph/examples/c_decode_loop_z000033 3 20000 | ||
|
|
||
| use std::env; | ||
| use std::fs; | ||
|
|
||
| use zstd::zstd_safe::zstd_sys; | ||
|
|
||
| fn main() { | ||
| let args: Vec<String> = env::args().collect(); | ||
| let level: i32 = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(3); | ||
| let iters: u32 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(20_000); | ||
| let corpus = args | ||
| .get(3) | ||
| .cloned() | ||
| .unwrap_or_else(|| "zstd/decodecorpus_files/z000033".to_string()); | ||
| let src = fs::read(&corpus).expect("read corpus"); | ||
| let n = src.len(); | ||
|
|
||
| let dst_cap = unsafe { zstd_sys::ZSTD_compressBound(n) }; | ||
| let mut compressed = vec![0u8; dst_cap]; | ||
| let csize = unsafe { | ||
| zstd_sys::ZSTD_compress( | ||
| compressed.as_mut_ptr() as *mut core::ffi::c_void, | ||
| dst_cap, | ||
| src.as_ptr() as *const core::ffi::c_void, | ||
| n, | ||
| level, | ||
| ) | ||
| }; | ||
| assert_eq!( | ||
| unsafe { zstd_sys::ZSTD_isError(csize) }, | ||
| 0, | ||
| "compress failed" | ||
| ); | ||
|
|
||
| let dctx = unsafe { zstd_sys::ZSTD_createDCtx() }; | ||
| assert!(!dctx.is_null(), "createDCtx failed"); | ||
| let mut out = vec![0u8; n]; | ||
| let mut total: u64 = 0; | ||
| for _ in 0..iters { | ||
| let w = unsafe { | ||
| zstd_sys::ZSTD_decompressDCtx( | ||
| dctx, | ||
| out.as_mut_ptr() as *mut core::ffi::c_void, | ||
| n, | ||
| compressed.as_ptr() as *const core::ffi::c_void, | ||
| csize, | ||
| ) | ||
| }; | ||
| assert_eq!(unsafe { zstd_sys::ZSTD_isError(w) }, 0, "decompress failed"); | ||
| assert_eq!(w, n, "decompress produced unexpected output size"); | ||
| total = total.wrapping_add(out.first().copied().unwrap_or(0) as u64); | ||
| } | ||
| unsafe { zstd_sys::ZSTD_freeDCtx(dctx) }; | ||
| eprintln!( | ||
| "c_decode_loop: level={level} iters={iters} csize={csize} out={n} sink={total} ({} blocks-ish)", | ||
| csize | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| //! Diagnostic: split each compressed block into its literals section | ||
| //! (Huffman) and sequences section (FSE) byte counts, for the pure-Rust | ||
| //! encoder vs the C FFI encoder, on a fixed (corpus, level). When the | ||
| //! sequence streams are byte-identical (see compare_ffi_sequences) but the | ||
| //! final size differs, this localizes the gap to literals vs sequences. | ||
| //! | ||
| //! Build: cargo build --release -p structured-zstd --example section_split --features dict_builder | ||
| //! Run: ./target/release/examples/section_split [corpus] [level] | ||
|
|
||
| use std::env; | ||
| use std::fs; | ||
|
|
||
| use structured_zstd::encoding::{CompressionLevel, compress_slice_to_vec}; | ||
| use zstd::zstd_safe::zstd_sys; | ||
|
|
||
| const MAGIC: u32 = 0xFD2F_B528; | ||
|
|
||
| /// Parse the literals-section header at `body[0..]`; return | ||
| /// `(lit_section_total_bytes, lit_type)`. lit_type: 0=Raw 1=RLE | ||
| /// 2=Compressed 3=Treeless. | ||
| fn lit_section_len(body: &[u8]) -> (usize, u8) { | ||
| let b0 = body[0] as usize; | ||
| let lit_type = (b0 & 0x3) as u8; | ||
| let sf = (b0 >> 2) & 0x3; | ||
| match lit_type { | ||
| 0 | 1 => { | ||
| // Raw / RLE: 1/2/3-byte header carrying Regenerated_Size. | ||
| let (hdr, regen) = match sf { | ||
| 0 | 2 => (1usize, b0 >> 3), | ||
| 1 => (2, (b0 >> 4) | ((body[1] as usize) << 4)), | ||
| _ => ( | ||
| 3, | ||
| (b0 >> 4) | ((body[1] as usize) << 4) | ((body[2] as usize) << 12), | ||
| ), | ||
| }; | ||
| // Raw payload = regen bytes; RLE payload = 1 byte. | ||
| let payload = if lit_type == 0 { regen } else { 1 }; | ||
| (hdr + payload, lit_type) | ||
| } | ||
| _ => { | ||
| // Compressed / Treeless: size fields start at bit 4. | ||
| // Fixed-width u64: the 5-byte (sf 3) header packs up to bit 35 | ||
| // (`body[4] << 28`), which truncates in a 32-bit `usize`. | ||
| let (hdr, compressed) = match sf { | ||
| 0 | 1 => { | ||
| let v = ((b0 as u64) >> 4) | ((body[1] as u64) << 4) | ((body[2] as u64) << 12); | ||
| (3, ((v >> 10) & 0x3FF) as usize) | ||
| } | ||
| 2 => { | ||
| let v = ((b0 as u64) >> 4) | ||
| | ((body[1] as u64) << 4) | ||
| | ((body[2] as u64) << 12) | ||
| | ((body[3] as u64) << 20); | ||
| (4, ((v >> 14) & 0x3FFF) as usize) | ||
| } | ||
| _ => { | ||
| let v = ((b0 as u64) >> 4) | ||
| | ((body[1] as u64) << 4) | ||
| | ((body[2] as u64) << 12) | ||
| | ((body[3] as u64) << 20) | ||
| | ((body[4] as u64) << 28); | ||
| (5, ((v >> 18) & 0x3FFFF) as usize) | ||
| } | ||
| }; | ||
| (hdr + compressed, lit_type) | ||
|
polaz marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Skip magic + frame header, returning the offset of the first block. | ||
| fn frame_header_len(frame: &[u8]) -> usize { | ||
| assert_eq!( | ||
| u32::from_le_bytes([frame[0], frame[1], frame[2], frame[3]]), | ||
| MAGIC, | ||
| "not a zstd frame" | ||
| ); | ||
| let fhd = frame[4]; | ||
| let single_segment = (fhd >> 5) & 1; | ||
| let checksum = (fhd >> 2) & 1; | ||
| let _ = checksum; | ||
| let dict_id_flag = fhd & 0x3; | ||
| let fcs_flag = (fhd >> 6) & 0x3; | ||
| let mut pos = 5usize; // magic(4) + FHD(1) | ||
| if single_segment == 0 { | ||
| pos += 1; // Window_Descriptor | ||
| } | ||
| pos += match dict_id_flag { | ||
| 0 => 0, | ||
| 1 => 1, | ||
| 2 => 2, | ||
| _ => 4, | ||
| }; | ||
| let fcs_bytes = match fcs_flag { | ||
| 0 => { | ||
| if single_segment == 1 { | ||
| 1 | ||
| } else { | ||
| 0 | ||
| } | ||
| } | ||
| 1 => 2, | ||
| 2 => 4, | ||
| _ => 8, | ||
| }; | ||
| pos + fcs_bytes | ||
| } | ||
|
|
||
| struct Split { | ||
| blocks: usize, | ||
| raw_blocks: usize, | ||
| rle_blocks: usize, | ||
| comp_blocks: usize, | ||
| lit_bytes: usize, | ||
| seq_bytes: usize, | ||
| lit_type_counts: [usize; 4], | ||
| } | ||
|
|
||
| fn analyze(frame: &[u8]) -> Split { | ||
| let mut pos = frame_header_len(frame); | ||
| let mut s = Split { | ||
| blocks: 0, | ||
| raw_blocks: 0, | ||
| rle_blocks: 0, | ||
| comp_blocks: 0, | ||
| lit_bytes: 0, | ||
| seq_bytes: 0, | ||
| lit_type_counts: [0; 4], | ||
| }; | ||
| loop { | ||
| let bh = | ||
| frame[pos] as u32 | ((frame[pos + 1] as u32) << 8) | ((frame[pos + 2] as u32) << 16); | ||
| let last = bh & 1; | ||
| let btype = (bh >> 1) & 0x3; | ||
| let bsize = (bh >> 3) as usize; | ||
| pos += 3; | ||
| s.blocks += 1; | ||
| match btype { | ||
| 0 => { | ||
| s.raw_blocks += 1; | ||
| pos += bsize; | ||
| } | ||
| 1 => { | ||
| s.rle_blocks += 1; | ||
| pos += 1; // physical RLE body is one byte | ||
| } | ||
| 2 => { | ||
| s.comp_blocks += 1; | ||
| let body = &frame[pos..pos + bsize]; | ||
| let (lit_total, lit_type) = lit_section_len(body); | ||
| assert!( | ||
| lit_total <= bsize, | ||
| "invalid block split: literals exceed block size \ | ||
| (lit_total={lit_total}, bsize={bsize})" | ||
| ); | ||
| s.lit_type_counts[lit_type as usize] += 1; | ||
| s.lit_bytes += lit_total; | ||
| s.seq_bytes += bsize - lit_total; | ||
| pos += bsize; | ||
|
polaz marked this conversation as resolved.
|
||
| } | ||
| _ => panic!("unexpected block type {btype}"), | ||
| } | ||
| if last == 1 { | ||
| break; | ||
| } | ||
| } | ||
| s | ||
| } | ||
|
|
||
| fn print_split(label: &str, total: usize, s: &Split) { | ||
| println!( | ||
| "{label}: total={total} blocks={} (raw={} rle={} comp={}) lit_section={} seq_section={} lit_types[raw/rle/comp/treeless]={:?}", | ||
| s.blocks, | ||
| s.raw_blocks, | ||
| s.rle_blocks, | ||
| s.comp_blocks, | ||
| s.lit_bytes, | ||
| s.seq_bytes, | ||
| s.lit_type_counts | ||
| ); | ||
| } | ||
|
|
||
| fn main() { | ||
| let corpus = env::args() | ||
| .nth(1) | ||
| .unwrap_or_else(|| "zstd/decodecorpus_files/z000033".to_string()); | ||
| let level: i32 = env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(1); | ||
| let bytes = fs::read(&corpus).expect("read corpus"); | ||
|
|
||
| let rust = compress_slice_to_vec(&bytes, CompressionLevel::Level(level)); | ||
|
|
||
| let cap = unsafe { zstd_sys::ZSTD_compressBound(bytes.len()) }; | ||
| let mut cbuf = vec![0u8; cap]; | ||
| let rc = unsafe { | ||
| zstd_sys::ZSTD_compress( | ||
| cbuf.as_mut_ptr() as *mut core::ffi::c_void, | ||
| cap, | ||
| bytes.as_ptr() as *const core::ffi::c_void, | ||
| bytes.len(), | ||
| level, | ||
| ) | ||
| }; | ||
| assert_eq!( | ||
| unsafe { zstd_sys::ZSTD_isError(rc) }, | ||
| 0, | ||
| "ZSTD_compress failed" | ||
| ); | ||
| let ffi = &cbuf[..rc]; | ||
|
|
||
| println!( | ||
| "=== section_split corpus={corpus} input={} level={level} ===", | ||
| bytes.len() | ||
| ); | ||
| let rs = analyze(&rust); | ||
| let fs_ = analyze(ffi); | ||
| print_split("rust", rust.len(), &rs); | ||
| print_split("ffi ", ffi.len(), &fs_); | ||
| println!( | ||
| "DELTA: total={:+} lit_section={:+} seq_section={:+}", | ||
| rust.len() as i64 - ffi.len() as i64, | ||
| rs.lit_bytes as i64 - fs_.lit_bytes as i64, | ||
| rs.seq_bytes as i64 - fs_.seq_bytes as i64, | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.