From a5ff31c79d0afe735d1e2e69504d5c49f80b3e02 Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 15:12:18 +0100 Subject: [PATCH 01/10] `addEvents_generic`: impl on `Fingerprint` --- lib/compress/zstd_preSplit.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 664f3c8a..77988d47 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -48,17 +48,19 @@ pub struct BorderStats { pub middleEvents: Fingerprint<256>, } -#[inline(always)] -fn addEvents_generic(fp: &mut Fingerprint, src: &[u8], samplingRate: usize, hashLog: c_uint) { - let limit = src.len().wrapping_sub(HASHLENGTH).wrapping_add(1); +impl Fingerprint { + #[inline(always)] + fn add_events(&mut self, src: &[u8], samplingRate: usize, hashLog: c_uint) { + debug_assert!(src.len() >= HASHLENGTH); + let limit = src.len() - HASHLENGTH + 1; + + for i in 0..limit.div_ceil(samplingRate) { + let n = i * samplingRate; + self.events[hash2([src[n], src[n + 1]], hashLog) as usize] += 1; + } - debug_assert!(src.len() >= HASHLENGTH); - for i in 0..limit.div_ceil(samplingRate) { - let n = i * samplingRate; - fp.events[hash2([src[n], src[n + 1]], hashLog) as usize] += 1; + self.nbEvents += limit / samplingRate; } - - fp.nbEvents += limit / samplingRate; } #[inline(always)] @@ -68,7 +70,7 @@ fn recordFingerprint_generic ) { fp.events[..1usize << HASH_LOG].fill(0); fp.nbEvents = 0; - addEvents_generic(fp, src, SAMPLING_RATE, HASH_LOG); + fp.add_events(src, SAMPLING_RATE, HASH_LOG); } fn fpDistance(fp1: &Fingerprint, fp2: &Fingerprint, hashLog: c_uint) -> u64 { From e844ce018955b5713dbe428b531dd56698770454 Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 15:15:25 +0100 Subject: [PATCH 02/10] `recordFingerprint_generic`: impl on `Fingerprint` --- lib/compress/zstd_preSplit.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 77988d47..6da8dd6c 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -61,16 +61,13 @@ impl Fingerprint { self.nbEvents += limit / samplingRate; } -} -#[inline(always)] -fn recordFingerprint_generic( - fp: &mut Fingerprint, - src: &[u8], -) { - fp.events[..1usize << HASH_LOG].fill(0); - fp.nbEvents = 0; - fp.add_events(src, SAMPLING_RATE, HASH_LOG); + #[inline(always)] + fn record(&mut self, src: &[u8]) { + self.events[..1usize << HASH_LOG].fill(0); + self.nbEvents = 0; + self.add_events(src, SAMPLING_RATE, HASH_LOG); + } } fn fpDistance(fp1: &Fingerprint, fp2: &Fingerprint, hashLog: c_uint) -> u64 { @@ -120,10 +117,10 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut // NOTE: the sampling rates go from coarse to fine, so that the hash log of each // entry matches the corresponding entry of `hashParams` below. static records_fs: [fn(&mut Fingerprint, &[u8]) -> (); 4] = [ - recordFingerprint_generic::<43, 8>, - recordFingerprint_generic::<11, 9>, - recordFingerprint_generic::<5, 10>, - recordFingerprint_generic::<1, 10>, + Fingerprint::record::<43, 8>, + Fingerprint::record::<11, 9>, + Fingerprint::record::<5, 10>, + Fingerprint::record::<1, 10>, ]; static hashParams: [c_uint; 4] = [8, 9, 10, 10]; From 34d956ef26c669b0f3225ff1c28f5b89aa1a7b9e Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 15:31:17 +0100 Subject: [PATCH 03/10] `fpDistance`: impl on `Fingerprint` --- lib/compress/zstd_preSplit.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 6da8dd6c..18e042e1 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -48,7 +48,7 @@ pub struct BorderStats { pub middleEvents: Fingerprint<256>, } -impl Fingerprint { +impl Fingerprint { #[inline(always)] fn add_events(&mut self, src: &[u8], samplingRate: usize, hashLog: c_uint) { debug_assert!(src.len() >= HASHLENGTH); @@ -68,20 +68,20 @@ impl Fingerprint { self.nbEvents = 0; self.add_events(src, SAMPLING_RATE, HASH_LOG); } -} -fn fpDistance(fp1: &Fingerprint, fp2: &Fingerprint, hashLog: c_uint) -> u64 { - let mut distance = 0u64; + fn distance(&self, other: &Self, hashLog: c_uint) -> u64 { + let mut distance = 0u64; - debug_assert!(1usize << hashLog <= N); + debug_assert!(1usize << hashLog <= N); - for n in 0..(1 << hashLog) { - let dst1 = fp1.events[n] as i64 * fp2.nbEvents as i64; - let dst2 = fp2.events[n] as i64 * fp1.nbEvents as i64; - distance = distance.wrapping_add(dst1.abs_diff(dst2)); - } + for n in 0..(1 << hashLog) { + let dst1 = self.events[n] as i64 * other.nbEvents as i64; + let dst2 = other.events[n] as i64 * self.nbEvents as i64; + distance += dst1.abs_diff(dst2); + } - distance + distance + } } /// Compares new events with past events. @@ -97,7 +97,7 @@ fn compareFingerprints( debug_assert!(newfp.nbEvents > 0); let p50 = ref_0.nbEvents * newfp.nbEvents; - let deviation = fpDistance(ref_0, newfp, hashLog); + let deviation = ref_0.distance(newfp, hashLog); let threshold = p50 as u64 * (THRESHOLD_BASE + penalty) as u64 / THRESHOLD_PENALTY_RATE as u64; deviation >= threshold } @@ -182,8 +182,8 @@ fn ZSTD_splitBlock_fromBorders(block: &[u8; 128 << 10], stats: &mut BorderStats) let middle = blockSize / 2 - segment / 2; HIST_add(&mut stats.middleEvents.events, &block[middle..][..segment]); stats.middleEvents.nbEvents = SEGMENT_SIZE as usize; - let distFromBegin = fpDistance(&stats.pastEvents, &stats.middleEvents, 8); - let distFromEnd = fpDistance(&stats.newEvents, &stats.middleEvents, 8); + let distFromBegin = stats.pastEvents.distance(&stats.middleEvents, 8); + let distFromEnd = stats.newEvents.distance(&stats.middleEvents, 8); let minDistance = (SEGMENT_SIZE * SEGMENT_SIZE / 3) as u64; if distFromBegin.abs_diff(distFromEnd) < minDistance { return 64 * (1 << 10); From be48ebe035e31da6a73285fcf7093174f0c8d253 Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 16:15:30 +0100 Subject: [PATCH 04/10] `compareFingerprints`: impl on `Fingerprint` --- lib/compress/zstd_preSplit.rs | 42 +++++++++++++++-------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 18e042e1..f78022d4 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -82,24 +82,20 @@ impl Fingerprint { distance } -} -/// Compares new events with past events. -/// -/// Returns `true` when the fingerprints are considered "too different". -fn compareFingerprints( - ref_0: &Fingerprint, - newfp: &Fingerprint, - penalty: c_int, - hashLog: c_uint, -) -> bool { - debug_assert!(ref_0.nbEvents > 0); - debug_assert!(newfp.nbEvents > 0); - - let p50 = ref_0.nbEvents * newfp.nbEvents; - let deviation = ref_0.distance(newfp, hashLog); - let threshold = p50 as u64 * (THRESHOLD_BASE + penalty) as u64 / THRESHOLD_PENALTY_RATE as u64; - deviation >= threshold + /// Compares new events with past events. + /// + /// Returns `true` when the fingerprints are considered "too different". + fn compare(&self, newfp: &Self, penalty: c_int, hashLog: c_uint) -> bool { + debug_assert!(self.nbEvents > 0); + debug_assert!(newfp.nbEvents > 0); + + let p50 = self.nbEvents * newfp.nbEvents; + let deviation = self.distance(newfp, hashLog); + let threshold = + p50 as u64 * (THRESHOLD_BASE + penalty) as u64 / THRESHOLD_PENALTY_RATE as u64; + deviation >= threshold + } } fn mergeEvents(acc: &mut Fingerprint, newfp: &Fingerprint) { @@ -134,12 +130,10 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut let mut pos = CHUNKSIZE as usize; while pos <= blockSize.wrapping_sub(CHUNKSIZE as usize) { record_f(&mut fpstats.newEvents, &block[pos..][..chunk]); - if compareFingerprints( - &fpstats.pastEvents, - &fpstats.newEvents, - penalty, - hashParams[level as usize], - ) { + if fpstats + .pastEvents + .compare(&fpstats.newEvents, penalty, hashParams[level as usize]) + { return pos; } else { mergeEvents(&mut fpstats.pastEvents, &fpstats.newEvents); @@ -176,7 +170,7 @@ fn ZSTD_splitBlock_fromBorders(block: &[u8; 128 << 10], stats: &mut BorderStats) HIST_add(&mut stats.newEvents.events, &block[blockSize - segment..]); stats.newEvents.nbEvents = SEGMENT_SIZE as usize; stats.pastEvents.nbEvents = stats.newEvents.nbEvents; - if !compareFingerprints(&stats.pastEvents, &stats.newEvents, 0, 8) { + if !stats.pastEvents.compare(&stats.newEvents, 0, 8) { return blockSize; } let middle = blockSize / 2 - segment / 2; From 80749585c83bb9583afd2fe1d5772b690b657d06 Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 16:19:42 +0100 Subject: [PATCH 05/10] `mergeEvents`: impl on `Fingerprint` --- lib/compress/zstd_preSplit.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index f78022d4..3c795bc0 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -96,13 +96,13 @@ impl Fingerprint { p50 as u64 * (THRESHOLD_BASE + penalty) as u64 / THRESHOLD_PENALTY_RATE as u64; deviation >= threshold } -} -fn mergeEvents(acc: &mut Fingerprint, newfp: &Fingerprint) { - for n in 0..HASHTABLESIZE { - acc.events[n] += newfp.events[n]; + fn merge_events(&mut self, newfp: &Self) { + for n in 0..N { + self.events[n] += newfp.events[n]; + } + self.nbEvents += newfp.nbEvents; } - acc.nbEvents += newfp.nbEvents; } pub const CHUNKSIZE: c_int = 8 << 10; @@ -136,7 +136,7 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut { return pos; } else { - mergeEvents(&mut fpstats.pastEvents, &fpstats.newEvents); + fpstats.pastEvents.merge_events(&fpstats.newEvents); if penalty > 0 { penalty -= 1; } From 199077b9a5ee1e26d82098aadee603746a2f8ece Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 16:22:07 +0100 Subject: [PATCH 06/10] `CHUNKSIZE`: make usize --- lib/compress/zstd_preSplit.rs | 10 +++++----- test-libzstd-rs-sys/src/compress.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 3c795bc0..3d16ba30 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -105,7 +105,7 @@ impl Fingerprint { } } -pub const CHUNKSIZE: c_int = 8 << 10; +pub const CHUNKSIZE: usize = 8 << 10; /// `fpstats` must be zeroed before each call. fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut FPStats) -> usize { @@ -124,11 +124,11 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut let record_f = records_fs[level as usize]; let mut penalty = THRESHOLD_PENALTY; - let chunk = CHUNKSIZE as usize; + let chunk = CHUNKSIZE; record_f(&mut fpstats.pastEvents, &block[..chunk]); - let mut pos = CHUNKSIZE as usize; - while pos <= blockSize.wrapping_sub(CHUNKSIZE as usize) { + let mut pos = CHUNKSIZE; + while pos <= blockSize.wrapping_sub(CHUNKSIZE) { record_f(&mut fpstats.newEvents, &block[pos..][..chunk]); if fpstats .pastEvents @@ -141,7 +141,7 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut penalty -= 1; } } - pos = pos.wrapping_add(CHUNKSIZE as usize); + pos = pos.wrapping_add(CHUNKSIZE); } debug_assert_eq!(pos, blockSize); blockSize diff --git a/test-libzstd-rs-sys/src/compress.rs b/test-libzstd-rs-sys/src/compress.rs index 67c0ec40..a3e0ce7c 100644 --- a/test-libzstd-rs-sys/src/compress.rs +++ b/test-libzstd-rs-sys/src/compress.rs @@ -1095,7 +1095,7 @@ mod pre_split { [usize; ZSTD_SLIPBLOCK_WORKSPACESIZE.div_ceil(size_of::())], >::uninit(); for boundary in [1, 8, 15, 16] { - let split = boundary * CHUNKSIZE as usize; + let split = boundary * CHUNKSIZE; block.fill(0); block[split..].fill(255); for level in 1..=4 { From 342bd9a44d5aba6153dc4d97ccf6494553766f21 Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 16:23:37 +0100 Subject: [PATCH 07/10] `SEGMENT_SIZE`: make usize --- lib/compress/zstd_preSplit.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 3d16ba30..582090f2 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -161,21 +161,21 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut /// /// `stats` must be zeroed before each call. fn ZSTD_splitBlock_fromBorders(block: &[u8; 128 << 10], stats: &mut BorderStats) -> usize { - const SEGMENT_SIZE: c_int = 512; + const SEGMENT_SIZE: usize = 512; let blockSize = block.len(); - let segment = SEGMENT_SIZE as usize; + let segment = SEGMENT_SIZE; HIST_add(&mut stats.pastEvents.events, &block[..segment]); HIST_add(&mut stats.newEvents.events, &block[blockSize - segment..]); - stats.newEvents.nbEvents = SEGMENT_SIZE as usize; + stats.newEvents.nbEvents = SEGMENT_SIZE; stats.pastEvents.nbEvents = stats.newEvents.nbEvents; if !stats.pastEvents.compare(&stats.newEvents, 0, 8) { return blockSize; } let middle = blockSize / 2 - segment / 2; HIST_add(&mut stats.middleEvents.events, &block[middle..][..segment]); - stats.middleEvents.nbEvents = SEGMENT_SIZE as usize; + stats.middleEvents.nbEvents = SEGMENT_SIZE; let distFromBegin = stats.pastEvents.distance(&stats.middleEvents, 8); let distFromEnd = stats.newEvents.distance(&stats.middleEvents, 8); let minDistance = (SEGMENT_SIZE * SEGMENT_SIZE / 3) as u64; From e77f0da8b0c16c2b01a20c14b1503284bf670e61 Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 16:43:57 +0100 Subject: [PATCH 08/10] Remove unused `BLOCKSIZE_MIN` --- lib/compress/zstd_preSplit.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 582090f2..2c11ff80 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -6,7 +6,6 @@ use crate::lib::compress::hist::HIST_add; pub const ZSTD_SLIPBLOCK_WORKSPACESIZE: usize = 8208; -pub const BLOCKSIZE_MIN: c_int = 3500; pub const THRESHOLD_PENALTY_RATE: c_int = 16; pub const THRESHOLD_BASE: c_int = THRESHOLD_PENALTY_RATE - 2; pub const THRESHOLD_PENALTY: c_int = 3; From 0b9af097a893f582bf4f4f4aa933dd3e06279ce0 Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 17:17:40 +0100 Subject: [PATCH 09/10] `ZSTD_splitBlock_byChunks`: use for loop --- lib/compress/zstd_preSplit.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 2c11ff80..6cfc061f 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -123,26 +123,23 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut let record_f = records_fs[level as usize]; let mut penalty = THRESHOLD_PENALTY; - let chunk = CHUNKSIZE; - - record_f(&mut fpstats.pastEvents, &block[..chunk]); - let mut pos = CHUNKSIZE; - while pos <= blockSize.wrapping_sub(CHUNKSIZE) { - record_f(&mut fpstats.newEvents, &block[pos..][..chunk]); + debug_assert!(blockSize.is_multiple_of(CHUNKSIZE)); + let (chunks, _) = block.as_chunks::(); + record_f(&mut fpstats.pastEvents, &chunks[0]); + for (i, chunk) in chunks[1..].iter().enumerate() { + record_f(&mut fpstats.newEvents, chunk); if fpstats .pastEvents .compare(&fpstats.newEvents, penalty, hashParams[level as usize]) { - return pos; + return (i + 1) * CHUNKSIZE; } else { fpstats.pastEvents.merge_events(&fpstats.newEvents); if penalty > 0 { penalty -= 1; } } - pos = pos.wrapping_add(CHUNKSIZE); } - debug_assert_eq!(pos, blockSize); blockSize } From 26178cd451436ea8535bde4cb1d26f1ae35e899b Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 9 Nov 2026 17:27:27 +0100 Subject: [PATCH 10/10] `ZSTD_splitBlock_*`: some more cleanup --- lib/compress/zstd_preSplit.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/compress/zstd_preSplit.rs b/lib/compress/zstd_preSplit.rs index 6cfc061f..1d2163a8 100644 --- a/lib/compress/zstd_preSplit.rs +++ b/lib/compress/zstd_preSplit.rs @@ -111,7 +111,7 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut let blockSize = block.len(); // NOTE: the sampling rates go from coarse to fine, so that the hash log of each // entry matches the corresponding entry of `hashParams` below. - static records_fs: [fn(&mut Fingerprint, &[u8]) -> (); 4] = [ + static records_fs: [fn(&mut Fingerprint, &[u8]); 4] = [ Fingerprint::record::<43, 8>, Fingerprint::record::<11, 9>, Fingerprint::record::<5, 10>, @@ -133,11 +133,10 @@ fn ZSTD_splitBlock_byChunks(block: &[u8; 128 << 10], level: c_int, fpstats: &mut .compare(&fpstats.newEvents, penalty, hashParams[level as usize]) { return (i + 1) * CHUNKSIZE; - } else { - fpstats.pastEvents.merge_events(&fpstats.newEvents); - if penalty > 0 { - penalty -= 1; - } + } + fpstats.pastEvents.merge_events(&fpstats.newEvents); + if penalty > 0 { + penalty -= 1; } } blockSize @@ -160,29 +159,32 @@ fn ZSTD_splitBlock_fromBorders(block: &[u8; 128 << 10], stats: &mut BorderStats) const SEGMENT_SIZE: usize = 512; let blockSize = block.len(); - let segment = SEGMENT_SIZE; - - HIST_add(&mut stats.pastEvents.events, &block[..segment]); - HIST_add(&mut stats.newEvents.events, &block[blockSize - segment..]); + HIST_add(&mut stats.pastEvents.events, &block[..SEGMENT_SIZE]); + HIST_add( + &mut stats.newEvents.events, + &block[blockSize - SEGMENT_SIZE..], + ); stats.newEvents.nbEvents = SEGMENT_SIZE; stats.pastEvents.nbEvents = stats.newEvents.nbEvents; if !stats.pastEvents.compare(&stats.newEvents, 0, 8) { return blockSize; } - let middle = blockSize / 2 - segment / 2; - HIST_add(&mut stats.middleEvents.events, &block[middle..][..segment]); + let middle = blockSize / 2 - SEGMENT_SIZE / 2; + HIST_add( + &mut stats.middleEvents.events, + &block[middle..][..SEGMENT_SIZE], + ); stats.middleEvents.nbEvents = SEGMENT_SIZE; let distFromBegin = stats.pastEvents.distance(&stats.middleEvents, 8); let distFromEnd = stats.newEvents.distance(&stats.middleEvents, 8); let minDistance = (SEGMENT_SIZE * SEGMENT_SIZE / 3) as u64; if distFromBegin.abs_diff(distFromEnd) < minDistance { - return 64 * (1 << 10); - } - (if distFromBegin > distFromEnd { + 64 * (1 << 10) + } else if distFromBegin > distFromEnd { 32 * (1 << 10) } else { 96 * (1 << 10) - }) as usize + } } /// Splits a block to find the optimal boundary for compression.