Skip to content
175 changes: 83 additions & 92 deletions lib/compress/zstd_preSplit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,109 +47,98 @@ 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<const N: usize> Fingerprint<N> {
#[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)]
fn record<const SAMPLING_RATE: usize, const HASH_LOG: c_uint>(&mut self, src: &[u8]) {
self.events[..1usize << HASH_LOG].fill(0);
self.nbEvents = 0;
self.add_events(src, SAMPLING_RATE, HASH_LOG);
}

#[inline(always)]
fn recordFingerprint_generic<const SAMPLING_RATE: usize, const HASH_LOG: c_uint>(
fp: &mut Fingerprint,
src: &[u8],
) {
fp.events[..1usize << HASH_LOG].fill(0);
fp.nbEvents = 0;
addEvents_generic(fp, src, SAMPLING_RATE, HASH_LOG);
}
fn distance(&self, other: &Self, hashLog: c_uint) -> u64 {
let mut distance = 0u64;

fn fpDistance<const N: usize>(fp1: &Fingerprint<N>, fp2: &Fingerprint<N>, 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 = 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);
}

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));
distance
}

distance
}

/// Compares new events with past events.
///
/// Returns `true` when the fingerprints are considered "too different".
fn compareFingerprints<const N: usize>(
ref_0: &Fingerprint<N>,
newfp: &Fingerprint<N>,
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 = fpDistance(ref_0, 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) {
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;
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 {
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] = [
recordFingerprint_generic::<43, 8>,
recordFingerprint_generic::<11, 9>,
recordFingerprint_generic::<5, 10>,
recordFingerprint_generic::<1, 10>,
static records_fs: [fn(&mut Fingerprint, &[u8]); 4] = [
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];
debug_assert!((0..=3).contains(&level));
let record_f = records_fs[level as usize];
let mut penalty = THRESHOLD_PENALTY;

let chunk = CHUNKSIZE as usize;

record_f(&mut fpstats.pastEvents, &block[..chunk]);
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],
) {
return pos;
} else {
mergeEvents(&mut fpstats.pastEvents, &fpstats.newEvents);
if penalty > 0 {
penalty -= 1;
}
debug_assert!(blockSize.is_multiple_of(CHUNKSIZE));
let (chunks, _) = block.as_chunks::<CHUNKSIZE>();
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 (i + 1) * CHUNKSIZE;
}
fpstats.pastEvents.merge_events(&fpstats.newEvents);
if penalty > 0 {
penalty -= 1;
}
pos = pos.wrapping_add(CHUNKSIZE as usize);
}
debug_assert_eq!(pos, blockSize);
blockSize
}

Expand All @@ -168,32 +156,35 @@ 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;

HIST_add(&mut stats.pastEvents.events, &block[..segment]);
HIST_add(&mut stats.newEvents.events, &block[blockSize - segment..]);
stats.newEvents.nbEvents = SEGMENT_SIZE as usize;
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 !compareFingerprints(&stats.pastEvents, &stats.newEvents, 0, 8) {
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;
let distFromBegin = fpDistance(&stats.pastEvents, &stats.middleEvents, 8);
let distFromEnd = fpDistance(&stats.newEvents, &stats.middleEvents, 8);
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.
Expand Down
2 changes: 1 addition & 1 deletion test-libzstd-rs-sys/src/compress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,7 @@ mod pre_split {
[usize; ZSTD_SLIPBLOCK_WORKSPACESIZE.div_ceil(size_of::<usize>())],
>::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 {
Expand Down
Loading