-
Notifications
You must be signed in to change notification settings - Fork 4
feat(cli): --show-default-cparams, --max, -B# and legacy training #529
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
36 commits
Select commit
Hold shift + click to select a range
770829b
feat(cli): --show-default-cparams, with a public LevelParameters
polaz 7804b25
fix(encoding): gate the post-split pass on the strategy
polaz cdd2152
feat(cli): --max, every knob at its hardest end
polaz 4ccdb7a
feat(cli): -B# cuts benchmark inputs into independent frames
polaz be020b2
feat(dictionary): the legacy trainer, and --train-legacy
polaz 5a4ccef
perf(dictionary): word compares and unchecked induction in legacy
polaz 126128d
perf(dictionary): SA-IS induction without the type array
polaz fde314f
perf(dictionary): name LMS substrings inside the suffix array
polaz fa57789
perf(dictionary): drop the padded suffix-array copy in legacy
polaz f09e0d0
perf(encoding): derive split-probe length codes once per block
polaz d2a3570
perf(encoding): borrow the split probes' Huffman table
polaz 0a15716
perf(encoding): build FSE tables in place without full-width clears
polaz 3644bf0
build(fse): gate the owning table builders on their consumers
polaz 43ac634
perf(encoding): make the optimal parser's price mode a const
polaz 32120ae
perf(encoding): compute integer-weight prices in place
polaz f3ae187
perf(encoding): walk the insertion tree on the stored index
polaz 342c49d
perf(encoding): run the optimal parser as one pass per block
polaz 21282a8
perf(encoding): search on the repeat history in place
polaz e20d1bb
perf(encoding): insert a tree catch-up run in one call
polaz ea02b1e
test(miri): let the aarch64 prefetch hints fall back to no-ops
polaz d9107b8
perf(encoding): let a match cell carry offset and length only
polaz 4513256
perf(encoding): take the tree's coordinates once per block
polaz 4305e19
fix(dictionary): stop the legacy neighbour walks at the noise slots
polaz ce2eeaf
docs: -M on legacy training, dictionary parity tests in CI, private d…
polaz 15a106d
perf(dictionary): borrow the legacy training corpus
polaz 923f0c1
perf(dictionary): build the suffix array in place
polaz bdb10fd
perf(dictionary): prefetch ahead of the induction sweeps
polaz 31438d5
perf(dictionary): prefetch ahead of the legacy analysis
polaz 1e5ee30
perf(dictionary): keep the recursion's bucket tables in the array
polaz 0992599
perf(dictionary): compare legacy prefixes with the vector kernel
polaz 6d09f03
perf(encoding): compare 32 bytes a step in the NEON prefix kernel
polaz 5e5c24c
fix: benchmark input checks, training sizes, probe table handles
polaz b5d9c9d
fix: bound benchmark frames and training sizes by their budget
polaz 1fda06a
fix(cli): build the 32-bit --max test without a Debug bound on Options
polaz 8255e81
fix(cli): split the training budget, sum sample sizes exactly
polaz 50ccfc4
fix(cli): read --block-size into the setting -B sets
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
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
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
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,134 @@ | ||
| //! The legacy trainer against the reference's `ZDICT_trainFromBuffer_legacy`: | ||
| //! both walk the same suffix array with the same selection rules, so the | ||
| //! content they choose has to be the same bytes. Only the header in front of it | ||
| //! differs, since the entropy tables are built by each side's own finalizer. | ||
| #![cfg(all(feature = "bench-internals", feature = "dict-builder"))] | ||
|
|
||
| use structured_zstd::testing::legacy_dict_content; | ||
| use zstd::zstd_safe::zstd_sys; | ||
|
|
||
| /// Every sample back to back, and the length of each. | ||
| type Samples = (Vec<u8>, Vec<usize>); | ||
|
|
||
| /// The reference's dictionary content for the same corpus: its dictionary with | ||
| /// the header (magic, id, entropy tables, repeat offsets) cut off. | ||
| fn reference_content( | ||
| samples: &[u8], | ||
| sizes: &[usize], | ||
| dict_size: usize, | ||
| selectivity: u32, | ||
| ) -> Option<Vec<u8>> { | ||
| let mut dict = vec![0u8; dict_size]; | ||
| let params = zstd_sys::ZDICT_legacy_params_t { | ||
| selectivityLevel: selectivity, | ||
| zParams: zstd_sys::ZDICT_params_t { | ||
| compressionLevel: 0, | ||
| notificationLevel: 0, | ||
| dictID: 0, | ||
| }, | ||
| }; | ||
| // SAFETY: every buffer is valid for the length passed with it, and | ||
| // `sizes` holds `sizes.len()` entries summing to `samples.len()`. | ||
| let written = unsafe { | ||
| zstd_sys::ZDICT_trainFromBuffer_legacy( | ||
| dict.as_mut_ptr().cast(), | ||
| dict.len(), | ||
| samples.as_ptr().cast(), | ||
| sizes.as_ptr(), | ||
| sizes.len() as u32, | ||
| params, | ||
| ) | ||
| }; | ||
| // SAFETY: plain query on a return code. | ||
| if written == 0 || unsafe { zstd_sys::ZDICT_isError(written) } != 0 { | ||
| return None; | ||
| } | ||
| dict.truncate(written); | ||
| // SAFETY: `dict` holds `written` bytes of the dictionary just built. | ||
| let header = unsafe { zstd_sys::ZDICT_getDictHeaderSize(dict.as_ptr().cast(), dict.len()) }; | ||
| // SAFETY: plain query on a return code. | ||
| assert_eq!(unsafe { zstd_sys::ZDICT_isError(header) }, 0); | ||
| Some(dict[header..].to_vec()) | ||
| } | ||
|
|
||
| /// Log lines of a few shapes, one sample each. | ||
| fn log_lines(count: u32) -> Samples { | ||
| const SHAPES: [&str; 4] = [ | ||
| "ts={i} level=INFO msg=\"flush memtable\" tenant=demo table=orders region=eu-west\n", | ||
| "ts={i} level=WARN msg=\"slow compaction\" tenant=demo table=users region=us-east\n", | ||
| "ts={i} level=INFO msg=\"rotate segment\" tenant=acme table=orders region=eu-west\n", | ||
| "ts={i} level=ERROR msg=\"write stalled\" tenant=acme table=events region=ap-south\n", | ||
| ]; | ||
| let mut samples = Vec::new(); | ||
| let mut sizes = Vec::new(); | ||
| for i in 0..count { | ||
| let line = SHAPES[(i % 4) as usize].replace("{i}", &format!("{:08}", i * 7919)); | ||
| sizes.push(line.len()); | ||
| samples.extend_from_slice(line.as_bytes()); | ||
| } | ||
| (samples, sizes) | ||
| } | ||
|
|
||
| /// The systemd unit files under `dict_tests/files`, one sample each. | ||
| fn unit_files() -> Samples { | ||
| let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../zstd/dict_tests/files"); | ||
| let mut names: Vec<_> = std::fs::read_dir(dir) | ||
| .expect("the fixture directory exists") | ||
| .map(|entry| entry.expect("readable entry").path()) | ||
| .filter(|path| path.extension().is_none_or(|ext| ext != "zst")) | ||
| .collect(); | ||
| names.sort(); | ||
| let mut samples = Vec::new(); | ||
| let mut sizes = Vec::new(); | ||
| for name in names { | ||
| let bytes = std::fs::read(&name).expect("readable fixture"); | ||
| if bytes.is_empty() { | ||
| continue; | ||
| } | ||
| sizes.push(bytes.len()); | ||
| samples.extend_from_slice(&bytes); | ||
| } | ||
| (samples, sizes) | ||
| } | ||
|
|
||
| /// The decodecorpus file cut into 4 KiB samples, as `--train -B4096` cuts it. | ||
| fn corpus_blocks() -> Samples { | ||
| let bytes = include_bytes!("../../zstd/decodecorpus_files/z000033").to_vec(); | ||
| let sizes = bytes.chunks(4096).map(<[u8]>::len).collect(); | ||
| (bytes, sizes) | ||
| } | ||
|
|
||
| /// Same corpus, same selectivity, same size: the same content, byte for byte. | ||
| #[test] | ||
| fn the_legacy_trainer_selects_the_references_content() { | ||
| let fixtures: [(&str, Samples); 3] = [ | ||
| ("log lines", log_lines(3000)), | ||
| ("unit files", unit_files()), | ||
| ("decodecorpus blocks", corpus_blocks()), | ||
| ]; | ||
| for (name, (samples, sizes)) in &fixtures { | ||
| for selectivity in [0u32, 4, 9, 12] { | ||
| for dict_size in [4096usize, 16 * 1024, 112_640] { | ||
| let ours = legacy_dict_content(samples, sizes, dict_size, selectivity); | ||
| let theirs = reference_content(samples, sizes, dict_size, selectivity); | ||
| let (Some(ours), Some(theirs)) = (ours, theirs) else { | ||
| panic!("{name} s={selectivity} size={dict_size}: one side trained nothing"); | ||
| }; | ||
| // The reference places its header in front of the content and | ||
| // lets it overwrite the front when both do not fit, so its | ||
| // content is ours or a tail of it. | ||
| assert!( | ||
| theirs.len() <= ours.len() && ours.ends_with(&theirs), | ||
| "{name} s={selectivity} size={dict_size}: {} content bytes against the \ | ||
| reference's {}", | ||
| ours.len(), | ||
| theirs.len(), | ||
| ); | ||
| assert!( | ||
| theirs.len() * 10 >= ours.len() * 9, | ||
| "{name} s={selectivity} size={dict_size}: the header should cost a sliver" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
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.