use crate::error::{Result, ZiporaError};
use crate::memory::fast_copy;
use crate::simd::{AdaptiveSimdSelector, Operation};
use std::marker::PhantomData;
use std::mem;
use std::time::Instant;
use crate::zipora_verify;
#[cfg(not(debug_assertions))]
mod performance_tests;
use int_vec_simd::{BitOps, PrefetchOps, SimdOps};
mod unaligned_ops {
use crate::memory::fast_copy;
pub struct UnalignedOps;
impl UnalignedOps {
#[inline]
#[allow(dead_code)]
pub unsafe fn read_u64_unaligned(ptr: *const u8) -> u64 {
unsafe { std::ptr::read_unaligned(ptr as *const u64) }
}
#[inline]
#[allow(dead_code)]
pub unsafe fn write_u64_unaligned(ptr: *mut u8, value: u64) {
unsafe {
std::ptr::write_unaligned(ptr as *mut u64, value);
}
}
#[inline]
#[allow(dead_code)]
pub unsafe fn read_bulk_u64(ptr: *const u8, count: usize, output: &mut [u64]) {
let byte_count = count * 8;
if byte_count >= 64 && count == output.len() {
let src_slice = unsafe { std::slice::from_raw_parts(ptr, byte_count) };
let dst_slice: &mut [u8] = bytemuck::cast_slice_mut(output);
if let Ok(()) = fast_copy(src_slice, dst_slice) {
return;
}
}
for (i, out) in output.iter_mut().take(count).enumerate() {
*out = unsafe { std::ptr::read_unaligned((ptr as *const u64).add(i)) };
}
}
#[inline]
#[allow(dead_code)]
pub unsafe fn write_bulk_u64(ptr: *mut u8, values: &[u64]) {
let byte_count = values.len() * 8;
if byte_count >= 64 {
let src_slice: &[u8] = bytemuck::cast_slice(values);
let dst_slice = unsafe { std::slice::from_raw_parts_mut(ptr, byte_count) };
if let Ok(()) = fast_copy(src_slice, dst_slice) {
return;
}
}
for (i, &value) in values.iter().enumerate() {
unsafe {
std::ptr::write_unaligned((ptr as *mut u64).add(i), value);
}
}
}
}
}
use unaligned_ops::UnalignedOps;
mod int_vec_simd {
pub struct BitOps;
impl BitOps {
#[inline]
pub fn compute_bit_width(value: u64) -> u8 {
if value == 0 {
1
} else {
64 - value.leading_zeros() as u8
}
}
#[inline]
pub fn extract_bits(value: u64, start: u8, count: u8) -> u64 {
if count == 0 {
return 0;
}
if count >= 64 {
return value >> start;
}
let mask = (1u64 << count) - 1;
(value >> start) & mask
}
}
pub struct SimdOps;
impl SimdOps {
#[inline]
pub fn analyze_range_bulk(values: &[u64]) -> (u64, u64) {
if values.is_empty() {
return (0, 0);
}
let mut min_val = values[0];
let mut max_val = values[0];
const SIMD_CHUNK_SIZE: usize = 8;
let (aligned_chunks, remainder) =
values.split_at((values.len() / SIMD_CHUNK_SIZE) * SIMD_CHUNK_SIZE);
for chunk in aligned_chunks.chunks_exact(SIMD_CHUNK_SIZE) {
for &value in chunk {
min_val = min_val.min(value);
max_val = max_val.max(value);
}
}
for &value in remainder {
min_val = min_val.min(value);
max_val = max_val.max(value);
}
(min_val, max_val)
}
#[inline]
#[allow(dead_code)]
pub fn analyze_range_bulk_unrolled(values: &[u64]) -> (u64, u64) {
if values.is_empty() {
return (0, 0);
}
if values.len() == 1 {
return (values[0], values[0]);
}
let mut min_val = values[0];
let mut max_val = values[0];
let mut i = 1;
while i + 3 < values.len() {
let v0 = values[i];
let v1 = values[i + 1];
let v2 = values[i + 2];
let v3 = values[i + 3];
min_val = min_val.min(v0).min(v1).min(v2).min(v3);
max_val = max_val.max(v0).max(v1).max(v2).max(v3);
i += 4;
}
while i < values.len() {
min_val = min_val.min(values[i]);
max_val = max_val.max(values[i]);
i += 1;
}
(min_val, max_val)
}
#[inline]
pub fn analyze_range_bulk_optimized(values: &[u64]) -> (u64, u64) {
if values.is_empty() {
return (0, 0);
}
if values.len() == 1 {
return (values[0], values[0]);
}
if values.len() <= 128 {
let mut min_val = values[0];
let mut max_val = values[0];
for &value in &values[1..] {
min_val = min_val.min(value);
max_val = max_val.max(value);
}
return (min_val, max_val);
}
let mut min_val = values[0];
let mut max_val = values[0];
const CHUNK_SIZE: usize = 16;
let chunk_count = values.len() / CHUNK_SIZE;
for chunk_idx in 0..chunk_count {
let start = chunk_idx * CHUNK_SIZE;
let chunk = &values[start..start + CHUNK_SIZE];
for &value in chunk {
min_val = min_val.min(value);
max_val = max_val.max(value);
}
}
for &value in &values[chunk_count * CHUNK_SIZE..] {
min_val = min_val.min(value);
max_val = max_val.max(value);
}
(min_val, max_val)
}
}
pub struct PrefetchOps;
impl PrefetchOps {
#[inline]
pub fn prefetch_read<T: ?Sized>(data: &T) {
let addr = data as *const T as *const u8;
#[cfg(target_arch = "x86_64")]
{
unsafe {
std::arch::x86_64::_mm_prefetch(
addr as *const i8,
std::arch::x86_64::_MM_HINT_T0,
);
}
}
#[cfg(not(target_arch = "x86_64"))]
{
let _ = addr;
}
}
#[inline]
#[allow(dead_code)]
pub fn prefetch_write<T: ?Sized>(data: &T) {
let addr = data as *const T as *const u8;
#[cfg(target_arch = "x86_64")]
{
unsafe {
std::arch::x86_64::_mm_prefetch(
addr as *const i8,
std::arch::x86_64::_MM_HINT_T0,
);
}
}
#[cfg(not(target_arch = "x86_64"))]
{
let _ = addr;
}
}
}
}
pub trait PackedInt: Copy + Clone + PartialEq + PartialOrd + std::fmt::Debug + 'static {
fn to_u64(self) -> u64;
fn from_u64(val: u64) -> Self;
fn max_value() -> Self;
fn min_value() -> Self;
fn bit_width() -> u8;
fn to_i64(self) -> i64;
fn from_i64(val: i64) -> Self;
}
macro_rules! impl_packed_int {
($t:ty, $bits:expr) => {
impl PackedInt for $t {
#[inline]
fn to_u64(self) -> u64 {
self as u64
}
#[inline]
fn from_u64(val: u64) -> Self {
val as Self
}
#[inline]
fn max_value() -> Self {
<$t>::MAX
}
#[inline]
fn min_value() -> Self {
<$t>::MIN
}
#[inline]
fn bit_width() -> u8 {
$bits
}
#[inline]
fn to_i64(self) -> i64 {
self as i64
}
#[inline]
fn from_i64(val: i64) -> Self {
val as Self
}
}
};
}
impl_packed_int!(u8, 8);
impl_packed_int!(u16, 16);
impl_packed_int!(u32, 32);
impl_packed_int!(u64, 64);
impl_packed_int!(i8, 8);
impl_packed_int!(i16, 16);
impl_packed_int!(i32, 32);
impl_packed_int!(i64, 64);
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlockSize {
Block64 = 6,
Block128 = 7,
}
impl BlockSize {
#[inline]
fn units(self) -> usize {
1 << (self as u8)
}
#[inline]
#[allow(dead_code)]
fn log2(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CompressionStrategy {
Raw,
MinMax { min_val: u64, bit_width: u8 },
BlockBased {
block_size: BlockSize,
offset_width: u8,
sample_width: u8,
is_sorted: bool,
},
Delta {
base_val: u64,
delta_width: u8,
is_uniform: bool,
uniform_delta: Option<u64>,
},
}
pub struct IntVec<T: PackedInt> {
strategy: CompressionStrategy,
data: Box<[u8]>,
index: Option<Box<[u8]>>,
len: usize,
stats: CompressionStats,
_marker: PhantomData<T>,
}
#[derive(Debug, Default, Clone)]
pub struct CompressionStats {
original_size: usize,
compressed_size: usize,
index_size: usize,
compression_time_ns: u64,
access_count: u64,
_cache_hits: u64,
}
impl<T: PackedInt> IntVec<T> {
pub fn new() -> Self {
Self {
strategy: CompressionStrategy::Raw,
data: Box::new([]),
index: None,
len: 0,
stats: CompressionStats::default(),
_marker: PhantomData,
}
}
#[inline]
pub fn from_slice_bulk(values: &[T]) -> Result<Self> {
Self::from_slice(values)
}
#[inline]
pub fn from_slice_bulk_simd(values: &[T]) -> Result<Self> {
Self::from_slice(values)
}
pub fn from_slice(values: &[T]) -> Result<Self> {
crate::zipora_verify!(
values.len() <= (isize::MAX as usize) / mem::size_of::<T>().max(1),
"input slice length {} too large for element size {}",
values.len(),
mem::size_of::<T>()
);
crate::zipora_verify!(
mem::size_of::<T>() >= 1 && mem::size_of::<T>() <= 8,
"element size {} must be between 1 and 8 bytes",
mem::size_of::<T>()
);
let start_time = std::time::Instant::now();
if values.is_empty() {
return Ok(Self::new());
}
let mut result = Self::new();
result.len = values.len();
let u64_values: Vec<u64> = values.iter().map(|v| v.to_u64()).collect();
let data_size_kb = std::mem::size_of_val(values) / 1024;
let strategy = if values.len() <= 10000 || data_size_kb <= 16 {
Self::analyze_small_dataset_strategy(&u64_values)
} else {
Self::analyze_optimal_strategy(&u64_values)
};
result.strategy = strategy;
result.compress_with_strategy(&u64_values, strategy)?;
result.stats.original_size = std::mem::size_of_val(values);
result.stats.compressed_size = result.data.len();
result.stats.index_size = result.index.as_ref().map_or(0, |idx| idx.len());
result.stats.compression_time_ns = start_time.elapsed().as_nanos() as u64;
Ok(result)
}
pub fn get(&self, index: usize) -> Option<T> {
crate::zipora_verify!(
self.len <= (isize::MAX as usize),
"vector length {} exceeds maximum",
self.len
);
crate::zipora_verify!(
self.data.len() <= (isize::MAX as usize),
"data size {} exceeds maximum",
self.data.len()
);
if index >= self.len {
return None;
}
crate::zipora_verify_bounds!(index, self.len);
let _ = self.stats.access_count.wrapping_add(1);
let result = match self.strategy {
CompressionStrategy::Raw => self.get_raw(index),
CompressionStrategy::MinMax { min_val, bit_width } => {
self.get_min_max(index, min_val, bit_width)
}
CompressionStrategy::BlockBased {
block_size,
offset_width,
sample_width,
is_sorted,
} => self.get_block_based(index, block_size, offset_width, sample_width, is_sorted),
CompressionStrategy::Delta {
base_val,
delta_width,
is_uniform,
uniform_delta,
..
} => self.get_delta(index, base_val, delta_width, is_uniform, uniform_delta),
};
result.map(T::from_u64)
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn compression_ratio(&self) -> f64 {
if self.stats.original_size == 0 {
1.0
} else {
let total_compressed = self.stats.compressed_size + self.stats.index_size;
total_compressed as f64 / self.stats.original_size as f64
}
}
pub fn stats(&self) -> &CompressionStats {
&self.stats
}
#[inline]
pub fn memory_usage(&self) -> usize {
mem::size_of::<Self>() + self.data.len() + self.index.as_ref().map_or(0, |idx| idx.len())
}
#[allow(dead_code)]
fn bulk_convert_to_u64(values: &[T]) -> Vec<u64> {
let mut u64_values = Vec::with_capacity(values.len());
let selector = AdaptiveSimdSelector::global();
let start_time = Instant::now();
const CHUNK_SIZE: usize = 128;
for chunk in values.chunks(CHUNK_SIZE) {
for &value in chunk {
u64_values.push(value.to_u64());
}
}
selector.monitor_performance(
Operation::Compress,
start_time.elapsed(),
values.len() as u64,
);
u64_values
}
#[allow(dead_code)]
fn from_slice_bulk_zerocopy(values: &[T], start_time: std::time::Instant) -> Result<Self> {
let mut result = Self::new();
result.len = values.len();
result.strategy = CompressionStrategy::Raw;
result.data = values
.iter()
.flat_map(|v| v.to_u64().to_le_bytes())
.collect();
result.stats.original_size = std::mem::size_of_val(values);
result.stats.compressed_size = result.data.len();
result.stats.index_size = 0;
result.stats.compression_time_ns = start_time.elapsed().as_nanos() as u64;
Ok(result)
}
fn analyze_small_dataset_strategy(values: &[u64]) -> CompressionStrategy {
if values.is_empty() {
return CompressionStrategy::Raw;
}
let len = values.len();
if len < 4 {
return CompressionStrategy::Raw;
}
let is_sorted = Self::fast_sorted_check(values);
if is_sorted && len >= 4 {
if let Some(uniform_delta) = Self::detect_uniform_delta(values) {
return CompressionStrategy::Delta {
base_val: values[0],
delta_width: if uniform_delta == 0 { 1 } else { 0 }, is_uniform: true,
uniform_delta: Some(uniform_delta),
};
}
return Self::analyze_delta_bulk(values);
}
let (min_val, max_val) = SimdOps::analyze_range_bulk_optimized(values);
if min_val == max_val {
return CompressionStrategy::MinMax {
min_val,
bit_width: 1,
};
}
let range = max_val - min_val;
let bit_width = BitOps::compute_bit_width(range);
if bit_width <= 16 || len <= 1000 {
return CompressionStrategy::MinMax { min_val, bit_width };
}
CompressionStrategy::BlockBased {
block_size: BlockSize::Block64, offset_width: bit_width.min(8), sample_width: 4, is_sorted, }
}
fn detect_uniform_delta(values: &[u64]) -> Option<u64> {
if values.len() < 2 {
return None;
}
let first_delta = if values[1] >= values[0] {
values[1] - values[0]
} else {
return None;
};
for i in 2..values.len() {
if values[i] < values[i - 1] {
return None;
}
if values[i] - values[i - 1] != first_delta {
return None;
}
}
Some(first_delta)
}
fn analyze_delta_bulk(values: &[u64]) -> CompressionStrategy {
if values.len() < 2 {
return CompressionStrategy::Raw;
}
let base_val = values[0];
let mut max_delta = 0u64;
let mut valid_delta = true;
let sample_step = (values.len() / 16).max(1);
for i in (sample_step..values.len()).step_by(sample_step) {
if let Some(delta) = values[i].checked_sub(values[i - sample_step]) {
max_delta = max_delta.max(delta);
} else {
valid_delta = false;
break;
}
}
if !valid_delta || max_delta > (1u64 << 32) {
return CompressionStrategy::Raw;
}
let delta_width = BitOps::compute_bit_width(max_delta);
CompressionStrategy::Delta {
base_val,
delta_width,
is_uniform: false,
uniform_delta: None,
}
}
fn fast_sorted_check(values: &[u64]) -> bool {
if values.len() < 2 {
return true;
}
let sample_step = (values.len() / 16).max(1);
let mut prev = values[0];
for i in (sample_step..values.len()).step_by(sample_step) {
if values[i] < prev {
return false;
}
prev = values[i];
}
true
}
fn analyze_optimal_strategy(values: &[u64]) -> CompressionStrategy {
if values.is_empty() {
return CompressionStrategy::Raw;
}
let len = values.len();
if len < 8 {
return CompressionStrategy::Raw;
}
let (min_val, max_val) = SimdOps::analyze_range_bulk(values);
let is_sorted = values.windows(2).all(|w| w[0] <= w[1]);
let strategies = [
Self::analyze_min_max(values, min_val, max_val),
Self::analyze_delta(values),
Self::analyze_block_based(values, is_sorted),
];
strategies
.into_iter()
.min_by(|a, b| {
let ratio_a = Self::estimate_compression_ratio(*a, len);
let ratio_b = Self::estimate_compression_ratio(*b, len);
ratio_a
.partial_cmp(&ratio_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap_or(CompressionStrategy::Raw)
}
fn analyze_min_max(_values: &[u64], min_val: u64, max_val: u64) -> CompressionStrategy {
if min_val == max_val {
return CompressionStrategy::MinMax {
min_val,
bit_width: 1,
};
}
let range = max_val - min_val;
let bit_width = BitOps::compute_bit_width(range);
CompressionStrategy::MinMax { min_val, bit_width }
}
fn analyze_delta(values: &[u64]) -> CompressionStrategy {
if values.len() < 2 {
return CompressionStrategy::Raw;
}
let base_val = values[0];
let mut max_delta = 0u64;
let mut valid_delta = true;
for i in 1..values.len() {
if let Some(delta) = values[i].checked_sub(values[i - 1]) {
max_delta = max_delta.max(delta);
} else {
valid_delta = false;
break;
}
}
if !valid_delta || max_delta > (1u64 << 32) {
return CompressionStrategy::Raw;
}
let delta_width = BitOps::compute_bit_width(max_delta);
CompressionStrategy::Delta {
base_val,
delta_width,
is_uniform: false,
uniform_delta: None,
}
}
fn analyze_block_based(values: &[u64], is_sorted: bool) -> CompressionStrategy {
let len = values.len();
if len < 64 {
return CompressionStrategy::Raw;
}
let block_size = if len >= 1024 {
BlockSize::Block128
} else {
BlockSize::Block64
};
let block_units = block_size.units();
let num_blocks = len.div_ceil(block_units);
let mut samples = Vec::with_capacity(num_blocks);
for block_idx in 0..num_blocks {
let start = block_idx * block_units;
let end = (start + block_units).min(len);
let block_min = values[start..end].iter().min().expect("non-empty block");
samples.push(*block_min);
}
let sample_min = *samples.iter().min().expect("non-empty samples");
let sample_max = *samples.iter().max().expect("non-empty samples");
let sample_width = BitOps::compute_bit_width(sample_max - sample_min);
let mut max_offset = 0u64;
for block_idx in 0..num_blocks {
let start = block_idx * block_units;
let end = (start + block_units).min(len);
let block_min = samples[block_idx];
for &val in &values[start..end] {
let offset = val - block_min;
max_offset = max_offset.max(offset);
}
}
let offset_width = BitOps::compute_bit_width(max_offset);
CompressionStrategy::BlockBased {
block_size,
offset_width,
sample_width,
is_sorted,
}
}
fn estimate_compression_ratio(strategy: CompressionStrategy, len: usize) -> f64 {
let original_size = len * 8;
let compressed_size = match strategy {
CompressionStrategy::Raw => original_size,
CompressionStrategy::MinMax { bit_width, .. } => {
(len * bit_width as usize).div_ceil(8).max(32)
}
CompressionStrategy::Delta { delta_width, .. } => {
8 + (len * delta_width as usize).div_ceil(8).max(32) }
CompressionStrategy::BlockBased {
block_size,
offset_width,
sample_width,
..
} => {
let block_units = block_size.units();
let num_blocks = len.div_ceil(block_units);
let index_size = num_blocks * sample_width as usize / 8;
let data_size = (len * offset_width as usize).div_ceil(8);
index_size + data_size
}
};
compressed_size as f64 / original_size as f64
}
fn compress_with_strategy(
&mut self,
values: &[u64],
strategy: CompressionStrategy,
) -> Result<()> {
match strategy {
CompressionStrategy::Raw => self.compress_raw(values),
CompressionStrategy::MinMax { min_val, bit_width } => {
self.compress_min_max(values, min_val, bit_width)
}
CompressionStrategy::BlockBased {
block_size,
offset_width,
sample_width,
is_sorted,
} => {
self.compress_block_based(values, block_size, offset_width, sample_width, is_sorted)
}
CompressionStrategy::Delta {
base_val,
delta_width,
is_uniform,
uniform_delta,
..
} => self.compress_delta(values, base_val, delta_width, is_uniform, uniform_delta),
}
}
fn compress_raw(&mut self, values: &[u64]) -> Result<()> {
let mut data = Vec::with_capacity(values.len() * 8);
for &value in values {
data.extend_from_slice(&value.to_le_bytes());
}
self.data = data.into_boxed_slice();
Ok(())
}
fn compress_min_max(&mut self, values: &[u64], min_val: u64, bit_width: u8) -> Result<()> {
if bit_width == 0 || bit_width > 64 {
return Err(ZiporaError::invalid_data("Invalid bit width"));
}
let total_bits = values.len() * bit_width as usize;
let byte_size = total_bits.div_ceil(8);
let aligned_size = (byte_size + 15) & !15;
let mut data = vec![0u8; aligned_size];
let mut bit_offset = 0;
for &value in values {
if value < min_val {
return Err(ZiporaError::invalid_data("Value below minimum"));
}
let offset_value = value - min_val;
self.write_bits(&mut data, offset_value, bit_offset, bit_width)?;
bit_offset += bit_width as usize;
}
self.data = data.into_boxed_slice();
Ok(())
}
fn compress_delta(
&mut self,
values: &[u64],
base_val: u64,
delta_width: u8,
is_uniform: bool,
uniform_delta: Option<u64>,
) -> Result<()> {
if values.is_empty() {
return Ok(());
}
if is_uniform && uniform_delta.is_some() {
let mut data = Vec::with_capacity(16); data.extend_from_slice(&base_val.to_le_bytes());
data.extend_from_slice(
&uniform_delta
.expect("uniform_delta set for uniform blocks")
.to_le_bytes(),
);
self.data = data.into_boxed_slice();
return Ok(());
}
let mut data = Vec::with_capacity(8 + values.len() * delta_width as usize / 8);
data.extend_from_slice(&base_val.to_le_bytes());
let total_bits = (values.len() - 1) * delta_width as usize;
let delta_bytes = total_bits.div_ceil(8);
let aligned_size = (delta_bytes + 15) & !15;
data.resize(8 + aligned_size, 0);
let mut bit_offset = 0;
for i in 1..values.len() {
let delta = values[i] - values[i - 1];
self.write_bits(&mut data[8..], delta, bit_offset, delta_width)?;
bit_offset += delta_width as usize;
}
self.data = data.into_boxed_slice();
Ok(())
}
fn compress_block_based(
&mut self,
values: &[u64],
block_size: BlockSize,
offset_width: u8,
sample_width: u8,
_is_sorted: bool,
) -> Result<()> {
let block_units = block_size.units();
let num_blocks = values.len().div_ceil(block_units);
let mut samples = Vec::with_capacity(num_blocks);
for block_idx in 0..num_blocks {
let start = block_idx * block_units;
let end = (start + block_units).min(values.len());
let block_min = *values[start..end].iter().min().expect("non-empty block");
samples.push(block_min);
}
let sample_min = *samples.iter().min().expect("non-empty samples");
let index_bits = num_blocks * sample_width as usize;
let index_bytes = index_bits.div_ceil(8);
let index_aligned = (index_bytes + 15) & !15;
let mut index_data = vec![0u8; index_aligned];
let mut bit_offset = 0;
for &sample in &samples {
let offset_sample = sample - sample_min;
self.write_bits(&mut index_data, offset_sample, bit_offset, sample_width)?;
bit_offset += sample_width as usize;
}
let data_bits = values.len() * offset_width as usize;
let data_bytes = data_bits.div_ceil(8);
let data_aligned = (data_bytes + 15) & !15;
let mut data = vec![0u8; data_aligned];
bit_offset = 0;
for block_idx in 0..num_blocks {
let start = block_idx * block_units;
let end = (start + block_units).min(values.len());
let block_min = samples[block_idx];
for i in start..end {
let offset = values[i] - block_min;
self.write_bits(&mut data, offset, bit_offset, offset_width)?;
bit_offset += offset_width as usize;
}
}
self.index = Some(index_data.into_boxed_slice());
self.data = data.into_boxed_slice();
Ok(())
}
fn write_bits(&self, data: &mut [u8], value: u64, bit_offset: usize, bits: u8) -> Result<()> {
let byte_offset = bit_offset / 8;
let bit_in_byte = bit_offset % 8;
if byte_offset >= data.len() || bits > 64 {
return Err(ZiporaError::invalid_data("Bit write out of bounds"));
}
let mask = if bits == 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
let masked_value = value & mask;
let bits_needed = bit_in_byte + bits as usize;
let bytes_needed = bits_needed.div_ceil(8);
if byte_offset + bytes_needed <= data.len() && bytes_needed <= 8 {
let mut buffer = [0u8; 8];
let available = (data.len() - byte_offset).min(8);
buffer[..available].copy_from_slice(&data[byte_offset..byte_offset + available]);
let current = u64::from_le_bytes(buffer);
let shifted_value = masked_value << bit_in_byte;
let result = current | shifted_value;
let result_bytes = result.to_le_bytes();
let copy_len = (data.len() - byte_offset).min(8);
data[byte_offset..byte_offset + copy_len].copy_from_slice(&result_bytes[..copy_len]);
} else {
for i in 0..bits {
let bit_pos = bit_offset + i as usize;
let byte_idx = bit_pos / 8;
let bit_idx = bit_pos % 8;
if byte_idx >= data.len() {
return Err(ZiporaError::invalid_data("Bit position out of bounds"));
}
if (masked_value >> i) & 1 == 1 {
data[byte_idx] |= 1 << bit_idx;
}
}
}
Ok(())
}
fn read_bits(&self, data: &[u8], bit_offset: usize, bits: u8) -> Result<u64> {
let byte_offset = bit_offset / 8;
let bit_in_byte = bit_offset % 8;
if byte_offset >= data.len() || bits > 64 {
return Err(ZiporaError::invalid_data("Bit read out of bounds"));
}
let mut buffer = [0u8; 8];
let available = (data.len() - byte_offset).min(8);
buffer[..available].copy_from_slice(&data[byte_offset..byte_offset + available]);
let value = u64::from_le_bytes(buffer);
Ok(BitOps::extract_bits(value, bit_in_byte as u8, bits))
}
fn get_raw(&self, index: usize) -> Option<u64> {
let byte_index = index * 8;
if byte_index + 8 <= self.data.len() {
if byte_index + 64 < self.data.len() {
PrefetchOps::prefetch_read(&self.data[byte_index + 64]);
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&self.data[byte_index..byte_index + 8]);
Some(u64::from_le_bytes(bytes))
} else {
None
}
}
fn get_min_max(&self, index: usize, min_val: u64, bit_width: u8) -> Option<u64> {
let bit_offset = index * bit_width as usize;
match self.read_bits(&self.data, bit_offset, bit_width) {
Ok(offset_value) => Some(min_val + offset_value),
Err(_) => None,
}
}
fn get_delta(
&self,
index: usize,
base_val: u64,
delta_width: u8,
is_uniform: bool,
uniform_delta: Option<u64>,
) -> Option<u64> {
if index == 0 {
return Some(base_val);
}
if is_uniform && let Some(delta) = uniform_delta {
return Some(base_val + (index as u64) * delta);
}
let bit_offset = (index - 1) * delta_width as usize;
match self.read_bits(&self.data[8..], bit_offset, delta_width) {
Ok(_delta) => {
let mut current_val = base_val;
for i in 1..=index {
let delta_offset = (i - 1) * delta_width as usize;
if let Ok(delta) = self.read_bits(&self.data[8..], delta_offset, delta_width) {
current_val += delta;
} else {
return None;
}
}
Some(current_val)
}
Err(_) => None,
}
}
fn get_block_based(
&self,
index: usize,
block_size: BlockSize,
offset_width: u8,
sample_width: u8,
_is_sorted: bool,
) -> Option<u64> {
let block_units = block_size.units();
let block_idx = index / block_units;
let index_data = self.index.as_ref()?;
let sample_bit_offset = block_idx * sample_width as usize;
let sample_offset = self
.read_bits(index_data, sample_bit_offset, sample_width)
.ok()?;
let data_bit_offset = index * offset_width as usize;
let block_offset = self
.read_bits(&self.data, data_bit_offset, offset_width)
.ok()?;
Some(sample_offset + block_offset)
}
}
impl<T: PackedInt> Default for IntVec<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: PackedInt> Clone for IntVec<T> {
fn clone(&self) -> Self {
Self {
strategy: self.strategy,
data: self.data.clone(),
index: self.index.clone(),
len: self.len,
stats: self.stats.clone(),
_marker: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let values = vec![1u32, 2, 3, 4, 5];
let vec = IntVec::from_slice(&values).unwrap();
assert_eq!(vec.len(), 5);
assert!(!vec.is_empty());
for (i, &expected) in values.iter().enumerate() {
assert_eq!(vec.get(i), Some(expected));
}
assert_eq!(vec.get(5), None);
}
#[test]
fn test_min_max_compression() {
let values: Vec<u32> = (1000..2000).collect();
let compressed = IntVec::from_slice(&values).unwrap();
for (i, &expected) in values.iter().enumerate() {
assert_eq!(compressed.get(i), Some(expected));
}
let ratio = compressed.compression_ratio();
assert!(ratio < 0.4, "Compression ratio {} should be < 0.4", ratio);
}
#[test]
fn test_different_integer_types() {
let u8_values: Vec<u8> = (0..255).collect();
let u8_compressed = IntVec::from_slice(&u8_values).unwrap();
assert_eq!(u8_compressed.get(100), Some(100));
let i32_values = vec![-100i32, -50, 0, 50, 100];
let i32_compressed = IntVec::from_slice(&i32_values).unwrap();
assert_eq!(i32_compressed.get(2), Some(0));
let u64_values = vec![u64::MAX - 10, u64::MAX - 5, u64::MAX];
let u64_compressed = IntVec::from_slice(&u64_values).unwrap();
assert_eq!(u64_compressed.get(2), Some(u64::MAX));
}
#[test]
fn test_sorted_sequence_compression() {
let values: Vec<u32> = (0..1000).collect();
let compressed = IntVec::from_slice(&values).unwrap();
match compressed.strategy {
CompressionStrategy::BlockBased { is_sorted, .. } => {
assert!(is_sorted, "Should detect sorted sequence");
}
_ => {
}
}
assert_eq!(compressed.get(0), Some(0));
assert_eq!(compressed.get(500), Some(500));
assert_eq!(compressed.get(999), Some(999));
let ratio = compressed.compression_ratio();
assert!(
ratio < 0.5,
"Should achieve good compression for sorted data"
);
}
#[test]
fn test_delta_compression() {
let mut values = vec![1000u32];
for i in 1..100 {
values.push(values[i - 1] + (i as u32 % 10) + 1);
}
let compressed = IntVec::from_slice(&values).unwrap();
for (i, &expected) in values.iter().enumerate() {
assert_eq!(compressed.get(i), Some(expected));
}
}
#[test]
fn test_empty_vector() {
let vec: IntVec<u32> = IntVec::new();
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert_eq!(vec.get(0), None);
assert_eq!(vec.compression_ratio(), 1.0);
}
#[test]
fn test_memory_usage() {
let values: Vec<u32> = (0..1000).collect();
let compressed = IntVec::from_slice(&values).unwrap();
let memory_usage = compressed.memory_usage();
let original_size = values.len() * 4;
println!(
"Memory usage: {} bytes vs {} bytes original",
memory_usage, original_size
);
println!("Compression ratio: {:.3}", compressed.compression_ratio());
assert!(memory_usage < original_size);
}
#[test]
fn test_statistics() {
let values: Vec<u32> = (0..100).collect();
let compressed = IntVec::from_slice(&values).unwrap();
let stats = compressed.stats();
assert!(stats.compression_time_ns > 0);
assert_eq!(stats.original_size, 400); assert!(stats.compressed_size < stats.original_size);
}
#[test]
fn test_simd_memory_operations() {
println!("=== SIMD Memory Operations Tests ===");
let large_size = 10_000;
let large_data: Vec<u64> = (0..large_size).map(|i| i as u64 * 17).collect();
let simd_vec = IntVec::from_slice_bulk_simd(&large_data).unwrap();
for (i, &expected) in large_data.iter().enumerate() {
assert_eq!(
simd_vec.get(i),
Some(expected as u64),
"SIMD mismatch at index {}",
i
);
}
println!("SIMD memory operations: {} elements verified", large_size);
println!("Compression ratio: {:.3}", simd_vec.compression_ratio());
}
#[test]
fn test_simd_edge_cases() {
println!("=== SIMD Edge Cases Tests ===");
let empty: Vec<u32> = vec![];
let empty_result = IntVec::from_slice_bulk_simd(&empty).unwrap();
assert_eq!(empty_result.len(), 0);
assert!(empty_result.is_empty());
let single = vec![42u32];
let single_result = IntVec::from_slice_bulk_simd(&single).unwrap();
assert_eq!(single_result.len(), 1);
assert_eq!(single_result.get(0), Some(42));
for size in 1..20 {
let small_data: Vec<u32> = (0..size).map(|i| i as u32).collect();
let result = IntVec::from_slice_bulk_simd(&small_data).unwrap();
assert_eq!(result.len(), size);
for (i, &expected) in small_data.iter().enumerate() {
assert_eq!(
result.get(i),
Some(expected),
"Small dataset mismatch at index {} (size {})",
i,
size
);
}
}
let identical = vec![1337u32; 1000];
let identical_result = IntVec::from_slice_bulk_simd(&identical).unwrap();
assert_eq!(identical_result.len(), 1000);
for i in 0..1000 {
assert_eq!(identical_result.get(i), Some(1337));
}
assert!(
identical_result.compression_ratio() < 0.1,
"Identical values should compress very well"
);
println!("Edge cases tested successfully");
}
}