use core::ops::{Deref, DerefMut};
pub const MIN_MATCH_LENGTH: usize = 3;
pub const MAX_MATCH_LENGTH: usize = 131074;
const HASH_LOG: usize = 16;
const HASH_SIZE: usize = 1 << HASH_LOG;
#[allow(dead_code)]
const HASH_MASK: u32 = (HASH_SIZE - 1) as u32;
#[allow(dead_code)]
const MAX_CHAIN_DEPTH: usize = 256;
const HASH_PRIME: u32 = 0x9E3779B9;
const HASH_PRIME2: u32 = 0x85EBCA6B;
#[repr(C, align(64))]
pub struct CacheAligned<T>(T);
impl<T> CacheAligned<T> {
#[inline]
pub const fn new(value: T) -> Self {
Self(value)
}
#[inline]
pub fn inner_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> Deref for CacheAligned<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for CacheAligned<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[repr(C, align(64))]
struct AlignedHashTable {
data: [u32; HASH_SIZE],
}
impl AlignedHashTable {
fn new_boxed() -> Box<Self> {
unsafe {
let layout = core::alloc::Layout::new::<Self>();
let ptr = std::alloc::alloc_zeroed(layout) as *mut Self;
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
Box::from_raw(ptr)
}
}
#[inline]
#[allow(dead_code)]
fn reset(&mut self) {
self.data.fill(0);
}
#[inline(always)]
fn get(&self, index: usize) -> u32 {
self.data[index]
}
#[inline(always)]
fn set(&mut self, index: usize, value: u32) {
self.data[index] = value;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Match {
pub position: usize,
pub offset: usize,
pub length: usize,
}
impl Match {
#[inline]
pub fn new(position: usize, offset: usize, length: usize) -> Self {
Self {
position,
offset,
length,
}
}
}
pub struct MatchFinder {
search_depth: usize,
hash_table: Box<AlignedHashTable>,
generation: u32,
chain_table: Vec<u32>,
input_len: usize,
predicted_offset: u32,
predicted_offset2: u32,
prediction_hits: u32,
}
impl core::fmt::Debug for MatchFinder {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MatchFinder")
.field("search_depth", &self.search_depth)
.field(
"hash_table",
&format_args!("[AlignedHashTable; {}]", HASH_SIZE),
)
.field("chain_table_len", &self.chain_table.len())
.field("input_len", &self.input_len)
.field("predicted_offset", &self.predicted_offset)
.field("predicted_offset2", &self.predicted_offset2)
.field("prediction_hits", &self.prediction_hits)
.finish()
}
}
const GEN_MASK: u32 = 0xF0000000;
const GEN_SHIFT: u32 = 28;
const POS_MASK: u32 = 0x0FFFFFFF;
impl MatchFinder {
pub fn new(search_depth: usize) -> Self {
Self {
search_depth: search_depth.clamp(1, 128),
hash_table: AlignedHashTable::new_boxed(),
generation: 0,
chain_table: Vec::new(),
input_len: 0,
predicted_offset: 0,
predicted_offset2: 0,
prediction_hits: 0,
}
}
#[inline]
pub fn early_exit_threshold(&self, position: usize) -> usize {
match position {
0..=1024 => 32, 1025..=8192 => 24, 8193..=32768 => 16, _ => 12, }
}
#[inline]
pub fn effective_depth(&self, input_len: usize) -> usize {
let base = self.search_depth;
let scaled = match input_len {
0..=4096 => base,
4097..=16384 => (base * 9 / 10).max(4),
16385..=65536 => (base * 3 / 4).max(4),
65537..=262144 => (base / 2).max(3),
_ => (base / 3).max(2),
};
scaled.min(base) }
#[inline]
pub(super) fn reset(&mut self, input_len: usize) {
self.generation = (self.generation + 1) & 0xF;
if self.chain_table.len() < input_len {
self.chain_table.resize(input_len, 0);
}
self.input_len = input_len;
self.predicted_offset = 0;
self.predicted_offset2 = 0;
self.prediction_hits = 0;
}
#[inline(always)]
pub(super) fn hash4(&self, data: &[u8], pos: usize) -> u32 {
debug_assert!(pos + 4 <= data.len());
let bytes = unsafe { std::ptr::read_unaligned(data.as_ptr().add(pos) as *const u32) };
bytes.wrapping_mul(HASH_PRIME) >> (32 - HASH_LOG as u32)
}
#[inline(always)]
pub(super) fn hash3(&self, data: &[u8], pos: usize) -> u32 {
debug_assert!(pos + 3 <= data.len());
let b0 = data[pos] as u32;
let b1 = data[pos + 1] as u32;
let b2 = data[pos + 2] as u32;
let value = b0 | (b1 << 8) | (b2 << 16);
value.wrapping_mul(HASH_PRIME) >> (32 - HASH_LOG as u32)
}
pub fn find_matches(&mut self, input: &[u8]) -> Vec<Match> {
if input.len() < MIN_MATCH_LENGTH {
return Vec::new();
}
self.reset(input.len());
let mut matches = Vec::with_capacity(input.len() / 16);
let mut pos = 0;
let end = input.len().saturating_sub(MIN_MATCH_LENGTH);
while pos <= end {
#[cfg(target_arch = "x86_64")]
if pos + 64 < input.len() {
unsafe {
use std::arch::x86_64::_mm_prefetch;
_mm_prefetch(
input.as_ptr().add(pos + 64) as *const i8,
std::arch::x86_64::_MM_HINT_T0,
);
}
}
let hash = if pos + 4 <= input.len() {
self.hash4(input, pos)
} else {
self.hash3(input, pos)
};
let cur_prefix =
unsafe { std::ptr::read_unaligned(input.as_ptr().add(pos) as *const u32) };
let mut best_match = None;
if self.predicted_offset > 0 && pos >= self.predicted_offset as usize {
let match_pos = pos - self.predicted_offset as usize;
let match_prefix = unsafe {
std::ptr::read_unaligned(input.as_ptr().add(match_pos) as *const u32)
};
if cur_prefix == match_prefix {
let length = 4 + self.match_length_from(input, match_pos + 4, pos + 4);
if length >= MIN_MATCH_LENGTH {
self.prediction_hits += 1;
best_match = Some(Match::new(pos, self.predicted_offset as usize, length));
}
}
}
if best_match.is_none()
&& self.predicted_offset2 > 0
&& self.predicted_offset2 != self.predicted_offset
&& pos >= self.predicted_offset2 as usize
{
let match_pos = pos - self.predicted_offset2 as usize;
let match_prefix = unsafe {
std::ptr::read_unaligned(input.as_ptr().add(match_pos) as *const u32)
};
if cur_prefix == match_prefix {
let length = 4 + self.match_length_from(input, match_pos + 4, pos + 4);
if length >= MIN_MATCH_LENGTH {
self.prediction_hits += 1;
best_match = Some(Match::new(pos, self.predicted_offset2 as usize, length));
}
}
}
if best_match.is_none() {
best_match = self.find_best_match(input, pos, hash as usize);
}
if let Some(m) = best_match {
matches.push(m);
let new_offset = m.offset as u32;
if new_offset != self.predicted_offset {
self.predicted_offset2 = self.predicted_offset;
self.predicted_offset = new_offset;
}
self.update_hash(input, pos, hash as usize);
if m.offset <= 4 && m.length >= 32 {
let skip_end = (pos + m.length).min(end);
let mut update_pos = pos + 16;
while update_pos < skip_end {
if update_pos + 4 <= input.len() {
let h = self.hash4(input, update_pos);
self.update_hash(input, update_pos, h as usize);
}
update_pos += 16;
}
} else if m.length >= 64 {
let skip_end = (pos + m.length).min(end);
let mut update_pos = pos + 8;
while update_pos < skip_end {
if update_pos + 4 <= input.len() {
let h = self.hash4(input, update_pos);
self.update_hash(input, update_pos, h as usize);
}
update_pos += 8;
}
} else if m.length >= 8 {
let skip_end = (pos + m.length).min(end);
let mut update_pos = pos + 4;
while update_pos < skip_end {
if update_pos + 4 <= input.len() {
let h = self.hash4(input, update_pos);
self.update_hash(input, update_pos, h as usize);
}
update_pos += 4;
}
}
pos += m.length;
} else {
self.update_hash(input, pos, hash as usize);
pos += 1;
}
}
matches
}
#[inline(always)]
pub(super) fn update_hash(&mut self, _input: &[u8], pos: usize, hash: usize) {
let prev = self.hash_table.get(hash);
let prev_gen = (prev & GEN_MASK) >> GEN_SHIFT;
let chain_entry = if prev_gen == self.generation {
prev
} else {
0
};
if pos < self.chain_table.len() {
self.chain_table[pos] = chain_entry;
}
let encoded = (self.generation << GEN_SHIFT) | ((pos + 1) as u32);
self.hash_table.set(hash, encoded);
}
#[inline]
pub(super) fn find_best_match(&self, input: &[u8], pos: usize, hash: usize) -> Option<Match> {
let hash_entry = self.hash_table.get(hash);
let entry_gen = (hash_entry & GEN_MASK) >> GEN_SHIFT;
if entry_gen != self.generation {
return None;
}
let entry_pos = hash_entry & POS_MASK;
if entry_pos == 0 {
return None;
}
let mut match_pos = (entry_pos - 1) as usize;
if match_pos >= pos || pos + 4 > input.len() {
return None;
}
let cur_prefix = unsafe { std::ptr::read_unaligned(input.as_ptr().add(pos) as *const u32) };
let mut best_match: Option<Match> = None;
let mut best_length = MIN_MATCH_LENGTH - 1;
let mut depth = 0;
let max_offset = 1 << 28;
let effective_depth = self.effective_depth(self.input_len);
while depth < effective_depth && match_pos < pos {
let offset = pos - match_pos;
if offset > max_offset {
break;
}
let next_chain = if match_pos < self.chain_table.len() {
let chain_entry = self.chain_table[match_pos];
let chain_gen = (chain_entry & GEN_MASK) >> GEN_SHIFT;
if chain_gen != self.generation {
0 } else {
let next_pos_enc = chain_entry & POS_MASK;
if next_pos_enc > 0 {
let next_pos = (next_pos_enc - 1) as usize;
#[cfg(target_arch = "x86_64")]
if next_pos < input.len() {
unsafe {
use std::arch::x86_64::_mm_prefetch;
_mm_prefetch(
input.as_ptr().add(next_pos) as *const i8,
std::arch::x86_64::_MM_HINT_T0,
);
}
}
}
next_pos_enc
}
} else {
0
};
let match_prefix = if match_pos + 4 <= input.len() {
unsafe { std::ptr::read_unaligned(input.as_ptr().add(match_pos) as *const u32) }
} else {
if next_chain == 0 {
break;
}
match_pos = (next_chain - 1) as usize;
depth += 1;
continue;
};
if match_prefix != cur_prefix {
if next_chain == 0 {
break;
}
match_pos = (next_chain - 1) as usize;
depth += 1;
continue;
}
let length = 4 + self.match_length_from(input, match_pos + 4, pos + 4);
let is_predicted = offset as u32 == self.predicted_offset;
let effective_length = if is_predicted && length >= MIN_MATCH_LENGTH {
length + 2
} else {
length
};
if effective_length > best_length {
best_length = length;
best_match = Some(Match::new(pos, offset, length));
let exit_threshold = self.early_exit_threshold(pos);
if length >= exit_threshold {
break;
}
}
if next_chain == 0 {
break; }
match_pos = (next_chain - 1) as usize;
depth += 1;
}
best_match
}
#[inline(always)]
pub fn match_length_from(&self, input: &[u8], pos1: usize, pos2: usize) -> usize {
if pos1 >= input.len() || pos2 >= input.len() {
return 0;
}
let max_len = (input.len() - pos2)
.min(input.len() - pos1)
.min(MAX_MATCH_LENGTH);
if max_len == 0 {
return 0;
}
#[cfg(feature = "simd")]
{
let src = &input[pos1..pos1 + max_len];
let cur = &input[pos2..pos2 + max_len];
haagenti_simd::find_match_length(src, cur, max_len)
}
#[cfg(not(feature = "simd"))]
{
let mut len = 0;
while len + 8 <= max_len {
let word1 = unsafe {
std::ptr::read_unaligned(input.as_ptr().add(pos1 + len) as *const u64)
};
let word2 = unsafe {
std::ptr::read_unaligned(input.as_ptr().add(pos2 + len) as *const u64)
};
let diff = word1 ^ word2;
if diff != 0 {
len += (diff.trailing_zeros() / 8) as usize;
return len;
}
len += 8;
}
while len < max_len && input[pos1 + len] == input[pos2 + len] {
len += 1;
}
len
}
}
#[inline]
pub fn find_matches_speculative(&mut self, input: &[u8]) -> Vec<Match> {
const LOOKAHEAD: usize = 4;
if input.len() < MIN_MATCH_LENGTH {
return Vec::new();
}
self.reset(input.len());
let mut matches = Vec::with_capacity(input.len() / 16);
let mut pos = 0;
let end = input.len().saturating_sub(MIN_MATCH_LENGTH + LOOKAHEAD);
while pos <= end && pos + 4 <= input.len() {
let hashes: [u32; LOOKAHEAD] = [
self.hash4(input, pos),
if pos + 5 <= input.len() {
self.hash4(input, pos + 1)
} else {
0
},
if pos + 6 <= input.len() {
self.hash4(input, pos + 2)
} else {
0
},
if pos + 7 <= input.len() {
self.hash4(input, pos + 3)
} else {
0
},
];
let mut best_match: Option<Match> = None;
let mut best_score: usize = 0;
for (i, &hash) in hashes.iter().enumerate() {
if hash == 0 {
continue;
}
let check_pos = pos + i;
if let Some(m) = self.find_best_match(input, check_pos, hash as usize) {
let score = m.length * 8 - i;
if score > best_score {
best_score = score;
best_match = Some(m);
}
}
}
if let Some(m) = best_match {
for (i, &hash) in hashes
.iter()
.enumerate()
.take(LOOKAHEAD.min(m.position - pos))
{
if pos + i + 4 <= input.len() {
self.update_hash(input, pos + i, hash as usize);
}
}
self.update_hash(input, m.position, hashes[m.position - pos] as usize);
matches.push(m);
if m.length >= 8 {
let skip_end = (m.position + m.length).min(end);
let mut update_pos = m.position + 4;
while update_pos < skip_end {
if update_pos + 4 <= input.len() {
let h = self.hash4(input, update_pos);
self.update_hash(input, update_pos, h as usize);
}
update_pos += 4;
}
}
pos = m.position + m.length;
} else {
self.update_hash(input, pos, hashes[0] as usize);
pos += 1;
}
}
let final_end = input.len().saturating_sub(MIN_MATCH_LENGTH);
while pos <= final_end && pos + 4 <= input.len() {
let hash = self.hash4(input, pos);
if let Some(m) = self.find_best_match(input, pos, hash as usize) {
self.update_hash(input, pos, hash as usize);
matches.push(m);
pos += m.length;
} else {
self.update_hash(input, pos, hash as usize);
pos += 1;
}
}
matches
}
}
#[derive(Debug)]
pub struct LazyMatchFinder {
pub(super) inner: MatchFinder,
lazy_threshold: usize,
}
impl LazyMatchFinder {
pub fn new(search_depth: usize) -> Self {
Self {
inner: MatchFinder::new(search_depth),
lazy_threshold: 24, }
}
#[inline]
pub fn configure_for_size(&mut self, input_len: usize) {
self.lazy_threshold = match input_len {
0..=4096 => 24, 4097..=16384 => 20, 16385..=65536 => 16, 65537..=262144 => 12, _ => 8, };
self.lazy_threshold = self.lazy_threshold.max(MIN_MATCH_LENGTH + 1);
}
#[inline]
pub fn find_matches_auto(&mut self, input: &[u8]) -> Vec<Match> {
self.configure_for_size(input.len());
if input.len() >= 131072 {
self.find_matches_chunked(input, 16384)
} else {
self.find_matches(input)
}
}
#[inline]
pub fn find_matches(&mut self, input: &[u8]) -> Vec<Match> {
if input.len() < MIN_MATCH_LENGTH {
return Vec::new();
}
self.inner.reset(input.len());
let mut matches = Vec::with_capacity(input.len() / 16);
let mut pos = 0;
let end = input.len().saturating_sub(MIN_MATCH_LENGTH);
let mut pending_match: Option<Match> = None;
let mut predicted_offset: usize = 0;
while pos <= end {
let hash = if pos + 4 <= input.len() {
self.inner.hash4(input, pos)
} else {
self.inner.hash3(input, pos)
};
let current_match =
if predicted_offset > 0 && pos >= predicted_offset && pos + 4 <= input.len() {
let match_pos = pos - predicted_offset;
let cur_prefix =
unsafe { std::ptr::read_unaligned(input.as_ptr().add(pos) as *const u32) };
let match_prefix = unsafe {
std::ptr::read_unaligned(input.as_ptr().add(match_pos) as *const u32)
};
if cur_prefix == match_prefix {
let length =
4 + self.inner.match_length_from(input, match_pos + 4, pos + 4);
if length >= MIN_MATCH_LENGTH {
Some(Match::new(pos, predicted_offset, length))
} else {
self.inner.find_best_match(input, pos, hash as usize)
}
} else {
self.inner.find_best_match(input, pos, hash as usize)
}
} else {
self.inner.find_best_match(input, pos, hash as usize)
};
if let Some(curr) = current_match {
predicted_offset = curr.offset;
if let Some(pending) = pending_match.take() {
if curr.length > pending.length + 1 {
matches.push(curr);
self.inner.update_hash(input, pos, hash as usize);
pos += curr.length;
} else {
matches.push(pending);
pos = pending.position + pending.length;
}
} else {
if curr.length >= self.lazy_threshold || pos + 1 > end {
matches.push(curr);
self.inner.update_hash(input, pos, hash as usize);
pos += curr.length;
} else {
pending_match = Some(curr);
self.inner.update_hash(input, pos, hash as usize);
pos += 1;
}
}
} else {
if let Some(pending) = pending_match.take() {
matches.push(pending);
pos = pending.position + pending.length;
} else {
self.inner.update_hash(input, pos, hash as usize);
pos += 1;
}
}
}
if let Some(pending) = pending_match {
matches.push(pending);
}
matches
}
#[inline]
pub fn find_matches_chunked(&mut self, input: &[u8], chunk_size: usize) -> Vec<Match> {
if input.len() <= chunk_size {
return self.find_matches(input);
}
let chunk_size = chunk_size.max(1024); let mut all_matches = Vec::with_capacity(input.len() / 16);
let mut chunk_start = 0;
const CHUNK_HASH_LOG: usize = 12;
const CHUNK_HASH_SIZE: usize = 1 << CHUNK_HASH_LOG;
const CHUNK_HASH_MASK: u32 = (CHUNK_HASH_SIZE - 1) as u32;
let mut chunk_hash = vec![0u32; CHUNK_HASH_SIZE];
let mut chunk_chain = vec![0u32; chunk_size];
let search_depth = self.inner.search_depth;
while chunk_start < input.len() {
let chunk_end = (chunk_start + chunk_size).min(input.len());
let chunk = &input[chunk_start..chunk_end];
if chunk.len() >= MIN_MATCH_LENGTH {
chunk_hash.fill(0);
if chunk_chain.len() < chunk.len() {
chunk_chain.resize(chunk.len(), 0);
}
chunk_chain[..chunk.len()].fill(0);
let chunk_matches = Self::find_matches_in_chunk(
chunk,
&mut chunk_hash,
&mut chunk_chain,
CHUNK_HASH_LOG,
CHUNK_HASH_MASK,
search_depth,
self.lazy_threshold,
);
for m in chunk_matches {
all_matches.push(Match::new(chunk_start + m.position, m.offset, m.length));
}
}
chunk_start = chunk_end;
}
all_matches
}
#[inline]
fn find_matches_in_chunk(
chunk: &[u8],
hash_table: &mut [u32],
chain_table: &mut [u32],
hash_log: usize,
hash_mask: u32,
search_depth: usize,
lazy_threshold: usize,
) -> Vec<Match> {
if chunk.len() < MIN_MATCH_LENGTH {
return Vec::new();
}
let mut matches = Vec::with_capacity(chunk.len() / 32);
let end = chunk.len().saturating_sub(MIN_MATCH_LENGTH);
let mut pos = 0;
let mut pending: Option<Match> = None;
while pos <= end {
let hash = if pos + 4 <= chunk.len() {
let bytes =
unsafe { std::ptr::read_unaligned(chunk.as_ptr().add(pos) as *const u32) };
let h = bytes.wrapping_mul(HASH_PRIME);
let h = h ^ (h >> 15);
let h = h.wrapping_mul(HASH_PRIME2);
(h >> (32 - hash_log as u32)) & hash_mask
} else {
let b0 = chunk[pos] as u32;
let b1 = chunk[pos + 1] as u32;
let b2 = chunk[pos + 2] as u32;
let value = b0 | (b1 << 8) | (b2 << 16);
(value.wrapping_mul(HASH_PRIME) >> (32 - hash_log as u32)) & hash_mask
};
let current_match = Self::find_best_match_in_chunk(
chunk,
pos,
hash as usize,
hash_table,
chain_table,
search_depth,
);
if let Some(curr) = current_match {
if let Some(pend) = pending.take() {
if curr.length > pend.length + 1 {
matches.push(curr);
Self::update_chunk_hash(pos, hash as usize, hash_table, chain_table);
pos += curr.length;
} else {
matches.push(pend);
pos = pend.position + pend.length;
}
} else if curr.length >= lazy_threshold || pos + 1 > end {
matches.push(curr);
Self::update_chunk_hash(pos, hash as usize, hash_table, chain_table);
pos += curr.length;
} else {
pending = Some(curr);
Self::update_chunk_hash(pos, hash as usize, hash_table, chain_table);
pos += 1;
}
} else if let Some(pend) = pending.take() {
matches.push(pend);
pos = pend.position + pend.length;
} else {
Self::update_chunk_hash(pos, hash as usize, hash_table, chain_table);
pos += 1;
}
}
if let Some(pend) = pending {
matches.push(pend);
}
matches
}
#[inline(always)]
fn update_chunk_hash(pos: usize, hash: usize, hash_table: &mut [u32], chain_table: &mut [u32]) {
let prev = hash_table[hash];
if pos < chain_table.len() {
chain_table[pos] = prev;
}
hash_table[hash] = (pos + 1) as u32;
}
#[inline]
fn find_best_match_in_chunk(
chunk: &[u8],
pos: usize,
hash: usize,
hash_table: &[u32],
chain_table: &[u32],
search_depth: usize,
) -> Option<Match> {
let hash_entry = hash_table[hash];
if hash_entry == 0 {
return None;
}
let mut match_pos = (hash_entry - 1) as usize;
if match_pos >= pos || pos + 4 > chunk.len() {
return None;
}
let cur_prefix = unsafe { std::ptr::read_unaligned(chunk.as_ptr().add(pos) as *const u32) };
let mut best_match: Option<Match> = None;
let mut best_length = MIN_MATCH_LENGTH - 1;
let mut depth = 0;
while depth < search_depth && match_pos < pos {
let offset = pos - match_pos;
if match_pos + 4 <= chunk.len() {
let match_prefix = unsafe {
std::ptr::read_unaligned(chunk.as_ptr().add(match_pos) as *const u32)
};
if match_prefix == cur_prefix {
let max_len = (chunk.len() - pos).min(chunk.len() - match_pos);
let mut length = 4;
while length < max_len && chunk[match_pos + length] == chunk[pos + length] {
length += 1;
}
if length > best_length {
best_length = length;
best_match = Some(Match::new(pos, offset, length));
if length >= 64 {
break; }
}
}
}
if match_pos < chain_table.len() {
let next = chain_table[match_pos];
if next == 0 {
break;
}
match_pos = (next - 1) as usize;
} else {
break;
}
depth += 1;
}
best_match
}
}
#[allow(dead_code)]
const LONG_HASH_LOG: usize = 14;
#[allow(dead_code)]
const LONG_HASH_SIZE: usize = 1 << LONG_HASH_LOG;
#[allow(dead_code)]
pub struct TwoTierMatchFinder {
short_hash: Box<AlignedHashTable>,
short_chain: Vec<u32>,
long_hash: Vec<u32>,
long_chain: Vec<u32>,
search_depth: usize,
input_len: usize,
}
impl core::fmt::Debug for TwoTierMatchFinder {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TwoTierMatchFinder")
.field(
"short_hash",
&format_args!("[AlignedHashTable; {}]", HASH_SIZE),
)
.field("short_chain_len", &self.short_chain.len())
.field("long_hash_len", &self.long_hash.len())
.field("long_chain_len", &self.long_chain.len())
.field("search_depth", &self.search_depth)
.field("input_len", &self.input_len)
.finish()
}
}
#[allow(dead_code)]
impl TwoTierMatchFinder {
pub fn new(search_depth: usize) -> Self {
Self {
short_hash: AlignedHashTable::new_boxed(),
short_chain: Vec::new(),
long_hash: vec![0u32; LONG_HASH_SIZE],
long_chain: Vec::new(),
search_depth: search_depth.clamp(1, 128),
input_len: 0,
}
}
fn reset(&mut self, input_len: usize) {
self.short_hash.reset();
if self.short_chain.len() < input_len {
self.short_chain.clear();
self.short_chain.resize(input_len, 0);
} else {
self.short_chain.truncate(input_len);
self.short_chain.fill(0);
}
self.long_hash.fill(0);
if self.long_chain.len() < input_len {
self.long_chain.clear();
self.long_chain.resize(input_len, 0);
} else {
self.long_chain.truncate(input_len);
self.long_chain.fill(0);
}
self.input_len = input_len;
}
#[inline(always)]
fn hash4(&self, data: &[u8], pos: usize) -> u32 {
debug_assert!(pos + 4 <= data.len());
let bytes = unsafe { std::ptr::read_unaligned(data.as_ptr().add(pos) as *const u32) };
let h = bytes.wrapping_mul(HASH_PRIME);
let h = h ^ (h >> 15);
let h = h.wrapping_mul(HASH_PRIME2);
h >> (32 - HASH_LOG as u32)
}
#[inline(always)]
fn hash8(&self, data: &[u8], pos: usize) -> u32 {
debug_assert!(pos + 8 <= data.len());
let bytes = unsafe { std::ptr::read_unaligned(data.as_ptr().add(pos) as *const u64) };
let h = (bytes as u32) ^ ((bytes >> 32) as u32);
let h = h.wrapping_mul(HASH_PRIME);
let h = h ^ (h >> 17);
let h = h.wrapping_mul(HASH_PRIME2);
h >> (32 - LONG_HASH_LOG as u32)
}
#[inline(always)]
fn update_hashes(&mut self, data: &[u8], pos: usize) {
if pos + 4 <= data.len() {
let h4 = self.hash4(data, pos) as usize;
let prev = self.short_hash.get(h4);
if pos < self.short_chain.len() {
self.short_chain[pos] = prev;
}
self.short_hash.set(h4, (pos + 1) as u32);
}
if pos + 8 <= data.len() {
let h8 = self.hash8(data, pos) as usize;
let prev = self.long_hash[h8];
if pos < self.long_chain.len() {
self.long_chain[pos] = prev;
}
self.long_hash[h8] = (pos + 1) as u32;
}
}
pub fn find_matches(&mut self, input: &[u8]) -> Vec<Match> {
if input.len() < MIN_MATCH_LENGTH {
return Vec::new();
}
self.reset(input.len());
let mut matches = Vec::with_capacity(input.len() / 16);
let mut pos = 0;
let end = input.len().saturating_sub(MIN_MATCH_LENGTH);
while pos <= end {
let mut best_match: Option<Match> = None;
if pos + 8 <= input.len() {
best_match = self.find_long_match(input, pos);
}
if best_match.is_none() && pos + 4 <= input.len() {
best_match = self.find_short_match(input, pos);
}
self.update_hashes(input, pos);
if let Some(m) = best_match {
matches.push(m);
if m.length >= 8 {
let skip_end = (pos + m.length).min(end);
let mut update_pos = pos + 4;
while update_pos < skip_end {
self.update_hashes(input, update_pos);
update_pos += 4;
}
}
pos += m.length;
} else {
pos += 1;
}
}
matches
}
#[inline]
fn find_long_match(&self, input: &[u8], pos: usize) -> Option<Match> {
let h8 = self.hash8(input, pos) as usize;
let hash_entry = self.long_hash[h8];
if hash_entry == 0 {
return None;
}
let mut match_pos = (hash_entry - 1) as usize;
if match_pos >= pos {
return None;
}
let cur_prefix = unsafe { std::ptr::read_unaligned(input.as_ptr().add(pos) as *const u64) };
let mut best_match: Option<Match> = None;
let mut best_length = 7; let mut depth = 0;
while depth < self.search_depth / 2 && match_pos < pos {
let offset = pos - match_pos;
if match_pos + 8 <= input.len() {
let match_prefix = unsafe {
std::ptr::read_unaligned(input.as_ptr().add(match_pos) as *const u64)
};
if match_prefix == cur_prefix {
let mut length = 8;
let max_len = (input.len() - pos).min(input.len() - match_pos);
while length < max_len && input[match_pos + length] == input[pos + length] {
length += 1;
}
if length > best_length {
best_length = length;
best_match = Some(Match::new(pos, offset, length));
if length >= 32 {
return best_match;
}
}
}
}
if match_pos < self.long_chain.len() {
let next = self.long_chain[match_pos];
if next == 0 {
break;
}
match_pos = (next - 1) as usize;
} else {
break;
}
depth += 1;
}
best_match
}
#[inline]
fn find_short_match(&self, input: &[u8], pos: usize) -> Option<Match> {
let h4 = self.hash4(input, pos) as usize;
let hash_entry = self.short_hash.get(h4);
if hash_entry == 0 {
return None;
}
let mut match_pos = (hash_entry - 1) as usize;
if match_pos >= pos {
return None;
}
let cur_prefix = unsafe { std::ptr::read_unaligned(input.as_ptr().add(pos) as *const u32) };
let mut best_match: Option<Match> = None;
let mut best_length = MIN_MATCH_LENGTH - 1;
let mut depth = 0;
while depth < self.search_depth && match_pos < pos {
let offset = pos - match_pos;
if match_pos + 4 <= input.len() {
let match_prefix = unsafe {
std::ptr::read_unaligned(input.as_ptr().add(match_pos) as *const u32)
};
if match_prefix == cur_prefix {
let mut length = 4;
let max_len = (input.len() - pos).min(input.len() - match_pos);
while length < max_len && input[match_pos + length] == input[pos + length] {
length += 1;
}
if length > best_length {
best_length = length;
best_match = Some(Match::new(pos, offset, length));
if length >= 24 {
return best_match;
}
}
}
}
if match_pos < self.short_chain.len() {
let next = self.short_chain[match_pos];
if next == 0 {
break;
}
match_pos = (next - 1) as usize;
} else {
break;
}
depth += 1;
}
best_match
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_match_creation() {
let m = Match::new(10, 5, 4);
assert_eq!(m.position, 10);
assert_eq!(m.offset, 5);
assert_eq!(m.length, 4);
}
#[test]
fn test_match_finder_creation() {
let mf = MatchFinder::new(16);
assert_eq!(mf.search_depth, 16);
}
#[test]
fn test_no_matches_short_input() {
let mut mf = MatchFinder::new(16);
let matches = mf.find_matches(b"ab");
assert!(matches.is_empty());
}
#[test]
fn test_no_matches_unique() {
let mut mf = MatchFinder::new(16);
let matches = mf.find_matches(b"abcdefghij");
assert!(matches.is_empty());
}
#[test]
fn test_simple_repeat() {
let mut mf = MatchFinder::new(16);
let matches = mf.find_matches(b"abcdabcd");
assert!(!matches.is_empty());
let m = &matches[0];
assert_eq!(m.position, 4);
assert_eq!(m.offset, 4);
assert_eq!(m.length, 4);
}
#[test]
fn test_overlapping_repeat() {
let mut mf = MatchFinder::new(16);
let matches = mf.find_matches(b"aaaaaaaaa");
assert!(!matches.is_empty());
let has_rle = matches.iter().any(|m| m.offset <= 4 && m.length >= 3);
assert!(has_rle);
}
#[test]
fn test_multiple_matches() {
let mut mf = MatchFinder::new(16);
let input = b"abcdXabcdYabcdZ";
let matches = mf.find_matches(input);
assert!(
matches.len() >= 1,
"Expected at least one match, got {:?}",
matches
);
}
#[test]
fn test_long_match() {
let mut mf = MatchFinder::new(16);
let input = b"0123456789ABCDEF0123456789ABCDEF";
let matches = mf.find_matches(input);
assert!(!matches.is_empty());
let m = &matches[0];
assert_eq!(m.length, 16); assert_eq!(m.offset, 16);
}
#[test]
fn test_hash_distribution() {
let mf = MatchFinder::new(16);
let input = b"testdatatestdata";
let h1 = mf.hash4(input, 0);
let h2 = mf.hash4(input, 4);
let h3 = mf.hash4(input, 8);
assert_eq!(h1, h3); assert_ne!(h1, h2); }
#[test]
fn test_search_depth_limit() {
let mut mf = MatchFinder::new(1);
let input = b"abcXabcYabcZ";
let matches = mf.find_matches(input);
assert!(matches.len() <= 3);
}
#[test]
fn test_match_length_calculation() {
let mut mf = MatchFinder::new(16);
let input = b"hellohello";
mf.reset(input.len());
let len = mf.match_length_from(input, 0, 5);
assert_eq!(len, 5); }
#[test]
fn test_lazy_match_finder() {
let mut mf = LazyMatchFinder::new(16);
let input = b"abcdefabcdefXabcdefabcdef";
let matches = mf.find_matches(input);
assert!(!matches.is_empty());
}
#[test]
fn test_large_input() {
let mut mf = MatchFinder::new(32);
let mut input = Vec::with_capacity(100_000);
let pattern = b"The quick brown fox jumps over the lazy dog. ";
while input.len() < 100_000 {
input.extend_from_slice(pattern);
}
let matches = mf.find_matches(&input);
assert!(
!matches.is_empty(),
"Expected to find matches in repetitive data"
);
let total_match_len: usize = matches.iter().map(|m| m.length).sum();
assert!(
total_match_len > input.len() / 4,
"Expected matches to cover at least 25% of input, got {} / {}",
total_match_len,
input.len()
);
}
#[test]
fn test_aligned_hash_table_alignment() {
let table = AlignedHashTable::new_boxed();
let addr = table.data.as_ptr() as usize;
assert_eq!(
addr % 64,
0,
"Hash table data should be 64-byte aligned, got address {:x}",
addr
);
let struct_addr = &*table as *const _ as usize;
assert_eq!(
struct_addr % 64,
0,
"AlignedHashTable struct should be 64-byte aligned, got address {:x}",
struct_addr
);
}
#[test]
fn test_aligned_hash_table_operations() {
let mut table = AlignedHashTable::new_boxed();
for i in 0..HASH_SIZE {
assert_eq!(table.get(i), 0);
}
table.set(0, 42);
table.set(HASH_SIZE - 1, 123);
assert_eq!(table.get(0), 42);
assert_eq!(table.get(HASH_SIZE - 1), 123);
table.reset();
assert_eq!(table.get(0), 0);
assert_eq!(table.get(HASH_SIZE - 1), 0);
}
#[test]
fn test_adaptive_depth_scales_with_size() {
let finder = MatchFinder::new(16);
assert_eq!(finder.effective_depth(1024), 16);
assert_eq!(finder.effective_depth(4096), 16);
assert_eq!(finder.effective_depth(8192), 14);
assert_eq!(finder.effective_depth(16384), 14);
assert_eq!(finder.effective_depth(65536), 12);
assert_eq!(finder.effective_depth(262144), 8);
}
#[test]
fn test_adaptive_depth_respects_minimum() {
let finder = MatchFinder::new(4);
assert!(finder.effective_depth(262144) >= 2);
assert!(finder.effective_depth(1_000_000) >= 2);
}
#[test]
fn test_adaptive_depth_respects_configured_max() {
let finder_shallow = MatchFinder::new(4);
let finder_deep = MatchFinder::new(64);
assert!(finder_shallow.effective_depth(1024) <= 4);
assert!(finder_deep.effective_depth(1024) <= 64);
let shallow_large = finder_shallow.effective_depth(65536);
let deep_large = finder_deep.effective_depth(65536);
assert!(deep_large > shallow_large);
}
#[test]
fn test_adaptive_finder_large_input_throughput() {
use std::time::Instant;
let mut data = Vec::with_capacity(65536);
let pattern = b"The quick brown fox jumps over the lazy dog. ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut finder = MatchFinder::new(16);
for _ in 0..3 {
let _ = finder.find_matches(&data);
}
let iterations = 20;
let start = Instant::now();
for _ in 0..iterations {
let _ = finder.find_matches(&data);
}
let elapsed = start.elapsed();
let total_bytes = data.len() * iterations;
let throughput_mib = total_bytes as f64 / elapsed.as_secs_f64() / (1024.0 * 1024.0);
assert!(
throughput_mib >= 30.0,
"Large input throughput {:.1} MiB/s below target 30 MiB/s",
throughput_mib
);
}
#[test]
fn test_adaptive_finder_maintains_compression_quality() {
let mut data = Vec::with_capacity(65536);
let pattern = b"compression test data with repeating patterns ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut finder = MatchFinder::new(16);
let matches = finder.find_matches(&data);
let total_match_bytes: usize = matches.iter().map(|m| m.length).sum();
let coverage = total_match_bytes as f64 / data.len() as f64;
assert!(
coverage >= 0.70,
"Match coverage {:.1}% below target 70%",
coverage * 100.0
);
}
#[test]
fn test_chunked_matching_correctness() {
let mut data = Vec::with_capacity(65536);
let pattern = b"The quick brown fox jumps over the lazy dog. ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut finder = LazyMatchFinder::new(16);
let standard_matches = finder.find_matches(&data);
let chunked_matches = finder.find_matches_chunked(&data, 16384);
for m in &standard_matches {
assert!(m.position + m.length <= data.len());
assert!(m.position >= m.offset);
}
for m in &chunked_matches {
assert!(m.position + m.length <= data.len());
assert!(m.position >= m.offset);
}
let chunked_coverage: usize = chunked_matches.iter().map(|m| m.length).sum();
let min_coverage = data.len() / 2; assert!(
chunked_coverage >= min_coverage,
"Chunked coverage {} below minimum {}",
chunked_coverage,
min_coverage
);
}
#[test]
fn test_chunked_matching_performance_reasonable() {
use std::time::Instant;
let mut data = Vec::with_capacity(262144);
let pattern = b"The quick brown fox jumps over the lazy dog. ";
for i in 0..262144 {
if i % 1024 < 512 {
data.push(pattern[i % pattern.len()]);
} else {
data.push((i as u8).wrapping_mul(17).wrapping_add(i as u8 >> 4));
}
}
let mut finder = LazyMatchFinder::new(16);
for _ in 0..2 {
let _ = finder.find_matches(&data);
let _ = finder.find_matches_chunked(&data, 16384);
}
let iterations = 10;
let start = Instant::now();
for _ in 0..iterations {
let _ = finder.find_matches(&data);
}
let standard_time = start.elapsed();
let start = Instant::now();
for _ in 0..iterations {
let _ = finder.find_matches_chunked(&data, 16384);
}
let chunked_time = start.elapsed();
let ratio = chunked_time.as_secs_f64() / standard_time.as_secs_f64();
assert!(
ratio < 20.0,
"Chunked ({:?}) is too slow compared to standard ({:?}), ratio: {:.2}x",
chunked_time,
standard_time,
ratio
);
}
#[test]
fn test_chunked_small_input_fallback() {
let mut finder = LazyMatchFinder::new(16);
let small_data = b"small input that fits in one chunk";
let matches = finder.find_matches_chunked(small_data, 16384);
assert!(matches.len() <= small_data.len());
}
#[test]
fn test_chunked_boundary_handling() {
let chunk_size = 1024;
let mut data = vec![b'A'; chunk_size - 8];
data.extend_from_slice(b"PATTERN!");
data.extend_from_slice(b"PATTERN!"); data.extend_from_slice(&vec![b'B'; chunk_size - 16]);
let mut finder = LazyMatchFinder::new(16);
let matches = finder.find_matches_chunked(&data, chunk_size);
for m in &matches {
assert!(m.position + m.length <= data.len());
let src_start = m.position - m.offset;
for i in 0..m.length {
assert_eq!(
data[src_start + i],
data[m.position + i],
"Match at {} offset {} length {} invalid at byte {}",
m.position,
m.offset,
m.length,
i
);
}
}
}
#[test]
fn test_chunked_maintains_compression_ratio() {
let mut data = Vec::with_capacity(65536);
let pattern = b"repeating content for compression ratio test ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut finder = LazyMatchFinder::new(16);
let standard = finder.find_matches(&data);
let chunked = finder.find_matches_chunked(&data, 16384);
let standard_coverage: usize = standard.iter().map(|m| m.length).sum();
let chunked_coverage: usize = chunked.iter().map(|m| m.length).sum();
let min_acceptable = (standard_coverage as f64 * 0.85) as usize;
assert!(
chunked_coverage >= min_acceptable,
"Chunked coverage {} below 85% of standard {}",
chunked_coverage,
standard_coverage
);
}
#[test]
fn test_early_exit_threshold_by_position() {
let finder = MatchFinder::new(16);
let threshold_early = finder.early_exit_threshold(100);
assert!(
threshold_early >= 24,
"Early position should have threshold >= 24, got {}",
threshold_early
);
let threshold_mid = finder.early_exit_threshold(10000);
assert!(
threshold_mid >= 12 && threshold_mid < 24,
"Mid position should have threshold 12-23, got {}",
threshold_mid
);
let threshold_late = finder.early_exit_threshold(50000);
assert!(
threshold_late >= 8 && threshold_late < 16,
"Late position should have threshold 8-15, got {}",
threshold_late
);
assert!(
threshold_early >= threshold_mid,
"Threshold should decrease with position"
);
assert!(
threshold_mid >= threshold_late,
"Threshold should decrease with position"
);
}
#[test]
fn test_early_exit_excellent_match() {
let mut finder = MatchFinder::new(16);
let mut data = vec![b'X'; 10]; let pattern = b"abcdefghijklmnopqrstuvwxyz0123456789ABCD";
data.extend_from_slice(pattern);
data.extend_from_slice(pattern);
let matches = finder.find_matches(&data);
let long_match = matches.iter().any(|m| m.length >= 36);
assert!(
long_match,
"Should find the long match >= 36 bytes, got {:?}",
matches
);
}
#[test]
fn test_early_exit_improves_throughput_on_repetitive() {
use std::time::Instant;
let mut data = Vec::with_capacity(65536);
let pattern = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut finder = MatchFinder::new(16);
for _ in 0..3 {
let _ = finder.find_matches(&data);
}
let iterations = 20;
let start = Instant::now();
for _ in 0..iterations {
let _ = finder.find_matches(&data);
}
let elapsed = start.elapsed();
let total_bytes = data.len() * iterations;
let throughput_mib = total_bytes as f64 / elapsed.as_secs_f64() / (1024.0 * 1024.0);
assert!(
throughput_mib >= 25.0,
"Repetitive data throughput {:.1} MiB/s below target 25 MiB/s",
throughput_mib
);
}
#[test]
fn test_early_exit_maintains_match_quality() {
let mut data = Vec::with_capacity(65536);
let pattern = b"The quick brown fox jumps over the lazy dog repeatedly. ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut finder = MatchFinder::new(16);
let matches = finder.find_matches(&data);
let total_match_bytes: usize = matches.iter().map(|m| m.length).sum();
let coverage = total_match_bytes as f64 / data.len() as f64;
assert!(
coverage >= 0.65,
"Match coverage {:.1}% below target 65%",
coverage * 100.0
);
}
#[test]
fn test_lazy_threshold_scaling_by_size() {
let mut finder = LazyMatchFinder::new(16);
finder.configure_for_size(1024);
assert_eq!(
finder.lazy_threshold, 24,
"Small input should use default threshold"
);
let mut finder = LazyMatchFinder::new(16);
finder.configure_for_size(16384);
assert!(
finder.lazy_threshold <= 20,
"Medium input should lower threshold"
);
let mut finder = LazyMatchFinder::new(16);
finder.configure_for_size(65536);
assert!(
finder.lazy_threshold <= 16,
"Large input should commit earlier"
);
let mut finder = LazyMatchFinder::new(16);
finder.configure_for_size(262144);
assert!(
finder.lazy_threshold <= 12,
"Very large input should be aggressive"
);
}
#[test]
fn test_adaptive_lazy_threshold_minimum() {
let mut finder = LazyMatchFinder::new(16);
finder.configure_for_size(1_000_000);
assert!(finder.lazy_threshold >= MIN_MATCH_LENGTH + 1);
}
#[test]
fn test_adaptive_lazy_maintains_quality() {
let mut data = Vec::with_capacity(65536);
let pattern = b"The quick brown fox jumps over the lazy dog. ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut fixed_finder = LazyMatchFinder::new(16);
let fixed_matches = fixed_finder.find_matches(&data);
let mut adaptive_finder = LazyMatchFinder::new(16);
adaptive_finder.configure_for_size(data.len());
let adaptive_matches = adaptive_finder.find_matches(&data);
let fixed_coverage: usize = fixed_matches.iter().map(|m| m.length).sum();
let adaptive_coverage: usize = adaptive_matches.iter().map(|m| m.length).sum();
let min_coverage = (fixed_coverage as f64 * 0.90) as usize;
assert!(
adaptive_coverage >= min_coverage,
"Adaptive coverage {} below 90% of fixed {}",
adaptive_coverage,
fixed_coverage
);
}
#[test]
fn test_find_matches_auto_uses_adaptive() {
let mut data = Vec::with_capacity(65536);
let pattern = b"repeating patterns for auto adaptive test. ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut finder = LazyMatchFinder::new(16);
let matches = finder.find_matches_auto(&data);
for m in &matches {
assert!(m.position + m.length <= data.len());
assert!(m.position >= m.offset);
}
let coverage: usize = matches.iter().map(|m| m.length).sum();
assert!(
coverage > data.len() / 2,
"Should cover at least 50% of input"
);
}
#[test]
fn test_two_tier_finder_creation() {
let finder = TwoTierMatchFinder::new(16);
assert_eq!(finder.search_depth, 16);
}
#[test]
fn test_two_tier_finds_matches() {
let mut finder = TwoTierMatchFinder::new(16);
let input = b"abcdefghijklmnopabcdefghijklmnop";
let matches = finder.find_matches(input);
assert!(!matches.is_empty(), "Should find matches");
let m = &matches[0];
assert_eq!(m.length, 16);
assert_eq!(m.offset, 16);
}
#[test]
fn test_two_tier_finds_long_matches_via_8byte_hash() {
let mut finder = TwoTierMatchFinder::new(16);
let mut data = vec![b'X'; 10];
let pattern = b"LONGPATTERN12345678901234567890AB";
data.extend_from_slice(pattern);
data.extend_from_slice(pattern);
let matches = finder.find_matches(&data);
let long_match = matches.iter().any(|m| m.length >= 30);
assert!(
long_match,
"Should find long match via 8-byte hash, got {:?}",
matches
);
}
#[test]
fn test_two_tier_finds_short_matches_fallback() {
let mut finder = TwoTierMatchFinder::new(16);
let input = b"ABCDxxABCD"; let matches = finder.find_matches(input);
let short_match = matches.iter().any(|m| m.length >= 4);
assert!(
short_match,
"Should find short match via 4-byte fallback, got {:?}",
matches
);
}
#[test]
fn test_two_tier_coverage_comparable_to_single() {
let mut data = Vec::with_capacity(16384);
let pattern = b"The quick brown fox jumps over the lazy dog. ";
while data.len() < 16384 {
data.extend_from_slice(pattern);
}
let mut single = MatchFinder::new(16);
let mut two_tier = TwoTierMatchFinder::new(16);
let single_matches = single.find_matches(&data);
let two_tier_matches = two_tier.find_matches(&data);
let single_coverage: usize = single_matches.iter().map(|m| m.length).sum();
let two_tier_coverage: usize = two_tier_matches.iter().map(|m| m.length).sum();
let min_coverage = (single_coverage as f64 * 0.90) as usize;
assert!(
two_tier_coverage >= min_coverage,
"Two-tier coverage {} below 90% of single {}",
two_tier_coverage,
single_coverage
);
}
#[test]
fn test_two_tier_performance_reasonable() {
use std::time::Instant;
let mut data = Vec::with_capacity(65536);
let pattern = b"repeating pattern for performance test data here. ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut single = MatchFinder::new(16);
let mut two_tier = TwoTierMatchFinder::new(16);
for _ in 0..3 {
let _ = single.find_matches(&data);
let _ = two_tier.find_matches(&data);
}
let iterations = 15;
let start = Instant::now();
for _ in 0..iterations {
let _ = single.find_matches(&data);
}
let single_time = start.elapsed();
let start = Instant::now();
for _ in 0..iterations {
let _ = two_tier.find_matches(&data);
}
let two_tier_time = start.elapsed();
let ratio = two_tier_time.as_secs_f64() / single_time.as_secs_f64();
assert!(
ratio < 30.0,
"Two-tier ({:?}) too slow compared to single ({:?}), ratio: {:.2}x",
two_tier_time,
single_time,
ratio
);
}
#[test]
fn test_two_tier_8byte_hash_distribution() {
let finder = TwoTierMatchFinder::new(16);
let data = b"AABBCCDDAABBCCDD";
let h1 = finder.hash8(data, 0); let h2 = finder.hash8(data, 8);
assert_eq!(h1, h2, "Same 8-byte sequence should have same hash");
let data2 = b"XYZ12345XYZ12345";
let h3 = finder.hash8(data2, 0);
assert!(h3 < LONG_HASH_SIZE as u32);
}
#[test]
fn test_speculative_finds_matches() {
let mut finder = MatchFinder::new(16);
let input = b"abcdefghijklmnopabcdefghijklmnop";
let matches = finder.find_matches_speculative(input);
assert!(!matches.is_empty(), "Should find matches");
let m = &matches[0];
assert_eq!(m.length, 16);
}
#[test]
fn test_speculative_correctness() {
let mut data = Vec::with_capacity(16384);
let pattern = b"The quick brown fox jumps over the lazy dog. ";
while data.len() < 16384 {
data.extend_from_slice(pattern);
}
let mut standard = MatchFinder::new(16);
let mut speculative = MatchFinder::new(16);
let std_matches = standard.find_matches(&data);
let spec_matches = speculative.find_matches_speculative(&data);
assert!(!std_matches.is_empty());
assert!(!spec_matches.is_empty());
let std_coverage: usize = std_matches.iter().map(|m| m.length).sum();
let spec_coverage: usize = spec_matches.iter().map(|m| m.length).sum();
let min_coverage = (std_coverage as f64 * 0.80) as usize;
assert!(
spec_coverage >= min_coverage,
"Speculative coverage {} below 80% of standard {}",
spec_coverage,
std_coverage
);
}
#[test]
fn test_speculative_no_overlapping_matches() {
let mut finder = MatchFinder::new(16);
let input = b"ABCABCABCABCABCABCABCABCABCABCABCABC";
let matches = finder.find_matches_speculative(input);
for i in 1..matches.len() {
let prev_end = matches[i - 1].position + matches[i - 1].length;
assert!(
matches[i].position >= prev_end,
"Match {} at pos {} overlaps with previous ending at {}",
i,
matches[i].position,
prev_end
);
}
}
#[test]
fn test_speculative_handles_short_input() {
let mut finder = MatchFinder::new(16);
let matches = finder.find_matches_speculative(b"abc");
assert!(matches.is_empty());
let matches = finder.find_matches_speculative(b"abcdabcd");
assert!(matches.len() <= 1);
}
#[test]
fn test_speculative_performance_reasonable() {
use std::time::Instant;
let mut data = Vec::with_capacity(65536);
let pattern = b"repeating pattern for speculative matching test. ";
while data.len() < 65536 {
data.extend_from_slice(pattern);
}
let mut standard = MatchFinder::new(16);
let mut speculative = MatchFinder::new(16);
for _ in 0..3 {
let _ = standard.find_matches(&data);
let _ = speculative.find_matches_speculative(&data);
}
let iterations = 15;
let start = Instant::now();
for _ in 0..iterations {
let _ = standard.find_matches(&data);
}
let std_time = start.elapsed();
let start = Instant::now();
for _ in 0..iterations {
let _ = speculative.find_matches_speculative(&data);
}
let spec_time = start.elapsed();
let ratio = spec_time.as_secs_f64() / std_time.as_secs_f64();
assert!(
ratio < 8.0,
"Speculative ({:?}) too slow compared to standard ({:?}), ratio: {:.2}x",
spec_time,
std_time,
ratio
);
}
#[test]
fn test_speculative_finds_better_matches() {
let mut finder = MatchFinder::new(16);
let mut data = vec![b'X'; 5];
data.extend_from_slice(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"); data.extend_from_slice(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
let matches = finder.find_matches_speculative(&data);
let has_long = matches.iter().any(|m| m.length >= 20);
assert!(
has_long,
"Speculative should find long matches: {:?}",
matches
);
}
}