use crate::error::{Result, ZiporaError};
use std::marker::PhantomData;
use std::mem;
use std::str;
pub trait OffsetOps<T> {
fn max_offset() -> usize;
fn to_usize(offset: &T) -> usize;
fn from_usize(value: usize) -> Result<T>;
fn type_name() -> &'static str;
}
#[derive(Debug, Clone)]
pub struct U32OffsetOps;
impl OffsetOps<u32> for U32OffsetOps {
fn max_offset() -> usize {
u32::MAX as usize
}
fn to_usize(offset: &u32) -> usize {
*offset as usize
}
fn from_usize(value: usize) -> Result<u32> {
if value > u32::MAX as usize {
return Err(ZiporaError::out_of_memory(value));
}
Ok(value as u32)
}
fn type_name() -> &'static str {
"u32"
}
}
#[derive(Debug, Clone)]
pub struct U64OffsetOps;
impl OffsetOps<u64> for U64OffsetOps {
fn max_offset() -> usize {
usize::MAX
}
fn to_usize(offset: &u64) -> usize {
*offset as usize
}
fn from_usize(value: usize) -> Result<u64> {
Ok(value as u64)
}
fn type_name() -> &'static str {
"u64"
}
}
#[repr(C, align(16))] #[derive(Clone, Copy, Debug)]
struct BitPackedEntry<T> {
packed_data: u64,
extended_data: u32,
_padding: u32,
_phantom: PhantomData<T>,
}
impl<T> BitPackedEntry<T>
where
T: Copy + std::fmt::Debug,
{
#[inline(always)]
fn new<O: OffsetOps<T>>(offset: T, length: usize) -> Result<Self> {
if O::to_usize(&offset) > O::max_offset() {
return Err(ZiporaError::out_of_memory(O::to_usize(&offset)));
}
if length > Self::max_length() {
return Err(ZiporaError::invalid_data(
format!("Length {} exceeds maximum {}", length, Self::max_length())
));
}
let packed_data = if mem::size_of::<T>() == 4 {
(O::to_usize(&offset) as u64) | ((length as u64) << 32)
} else {
(O::to_usize(&offset) as u64 & 0x000000FFFFFFFFFF) | ((length as u64) << 40)
};
Ok(Self {
packed_data,
extended_data: 0, _padding: 0,
_phantom: PhantomData,
})
}
#[inline(always)]
fn offset<O: OffsetOps<T>>(&self) -> T {
#[cfg(target_feature = "bmi2")]
{
self.offset_bmi2::<O>()
}
#[cfg(not(target_feature = "bmi2"))]
{
self.offset_fallback::<O>()
}
}
#[inline(always)]
fn length(&self) -> usize {
#[cfg(target_feature = "bmi2")]
{
self.length_bmi2()
}
#[cfg(not(target_feature = "bmi2"))]
{
self.length_fallback()
}
}
const fn max_length() -> usize {
if mem::size_of::<T>() == 4 {
u32::MAX as usize } else {
(1usize << 24) - 1 }
}
#[cfg(target_feature = "bmi2")]
#[inline(always)]
fn offset_bmi2<O: OffsetOps<T>>(&self) -> T {
#[cfg(target_arch = "x86_64")]
unsafe {
if mem::size_of::<T>() == 4 {
let extracted = std::arch::x86_64::_bextr_u64(self.packed_data, 0, 32);
O::from_usize(extracted as usize).unwrap_or_else(|_| unreachable!())
} else {
let extracted = std::arch::x86_64::_bextr_u64(self.packed_data, 0, 40);
O::from_usize(extracted as usize).unwrap_or_else(|_| unreachable!())
}
}
#[cfg(not(target_arch = "x86_64"))]
{
self.offset_fallback::<O>()
}
}
#[cfg(target_feature = "bmi2")]
#[inline(always)]
fn length_bmi2(&self) -> usize {
#[cfg(target_arch = "x86_64")]
unsafe {
if mem::size_of::<T>() == 4 {
std::arch::x86_64::_bextr_u64(self.packed_data, 32, 32) as usize
} else {
std::arch::x86_64::_bextr_u64(self.packed_data, 40, 24) as usize
}
}
#[cfg(not(target_arch = "x86_64"))]
{
self.length_fallback()
}
}
#[inline(always)]
fn offset_fallback<O: OffsetOps<T>>(&self) -> T {
if mem::size_of::<T>() == 4 {
let offset_val = (self.packed_data & 0xFFFFFFFF) as usize;
O::from_usize(offset_val).unwrap_or_else(|_| unreachable!())
} else {
let offset_val = (self.packed_data & 0x000000FFFFFFFFFF) as usize;
O::from_usize(offset_val).unwrap_or_else(|_| unreachable!())
}
}
#[inline(always)]
fn length_fallback(&self) -> usize {
if mem::size_of::<T>() == 4 {
(self.packed_data >> 32) as usize
} else {
((self.packed_data >> 40) & 0xFFFFFF) as usize
}
}
#[inline(always)]
fn end_offset<O: OffsetOps<T>>(&self) -> usize {
O::to_usize(&self.offset::<O>()) + self.length()
}
}
impl<T> PartialEq for BitPackedEntry<T> {
fn eq(&self, other: &Self) -> bool {
self.packed_data == other.packed_data && self.extended_data == other.extended_data
}
}
impl<T> Eq for BitPackedEntry<T> {}
#[derive(Debug, Clone)]
pub struct BitPackedConfig {
pub initial_arena_capacity: usize,
pub initial_index_capacity: usize,
pub enable_hardware_acceleration: bool,
pub use_memory_mapping: bool,
pub simd_alignment: usize,
}
impl Default for BitPackedConfig {
fn default() -> Self {
Self {
initial_arena_capacity: 8 * 1024,
initial_index_capacity: 512,
enable_hardware_acceleration: true,
use_memory_mapping: false,
simd_alignment: 16,
}
}
}
impl BitPackedConfig {
pub fn performance_optimized() -> Self {
Self {
initial_arena_capacity: 128 * 1024,
initial_index_capacity: 2048,
enable_hardware_acceleration: true,
use_memory_mapping: false,
simd_alignment: 32, }
}
pub fn memory_optimized() -> Self {
Self {
initial_arena_capacity: 4 * 1024,
initial_index_capacity: 256,
enable_hardware_acceleration: true,
use_memory_mapping: false,
simd_alignment: 16,
}
}
pub fn large_dataset() -> Self {
Self {
initial_arena_capacity: 1024 * 1024,
initial_index_capacity: 8192,
enable_hardware_acceleration: true,
use_memory_mapping: true,
simd_alignment: 64, }
}
}
#[derive(Debug, Default, Clone)]
pub struct BitPackedStats {
pub total_strings: usize,
pub arena_bytes_used: usize,
pub index_bytes_used: usize,
pub memory_savings_percent: f64,
pub hardware_acceleration_enabled: bool,
pub average_string_length: f64,
}
pub struct BitPackedStringVec<T, O>
where
T: Copy + std::fmt::Debug,
O: OffsetOps<T>,
{
arena: Vec<u8>,
entries: Vec<BitPackedEntry<T>>,
config: BitPackedConfig,
stats: BitPackedStats,
_offset_ops: PhantomData<O>,
}
impl<T, O> BitPackedStringVec<T, O>
where
T: Copy + std::fmt::Debug,
O: OffsetOps<T>,
{
pub fn new() -> Self {
Self::with_config(BitPackedConfig::default())
}
pub fn with_config(config: BitPackedConfig) -> Self {
let mut arena = Vec::with_capacity(config.initial_arena_capacity);
if config.simd_alignment > 1 {
arena.reserve(config.simd_alignment);
}
let mut vec = Self {
arena,
entries: Vec::with_capacity(config.initial_index_capacity),
config,
stats: BitPackedStats::default(),
_offset_ops: PhantomData,
};
vec.stats.hardware_acceleration_enabled = vec.has_hardware_acceleration();
vec
}
pub fn with_capacity(capacity: usize) -> Self {
let mut config = BitPackedConfig::default();
config.initial_index_capacity = capacity;
config.initial_arena_capacity = capacity * 20; Self::with_config(config)
}
pub fn push(&mut self, s: &str) -> Result<usize> {
let s_bytes = s.as_bytes();
let offset = O::from_usize(self.arena.len())?;
let length = s_bytes.len();
if O::to_usize(&offset) + length > O::max_offset() {
return Err(ZiporaError::out_of_memory(O::to_usize(&offset) + length));
}
self.arena.extend_from_slice(s_bytes);
let entry = BitPackedEntry::new::<O>(offset, length)?;
let index = self.entries.len();
self.entries.push(entry);
self.update_stats();
Ok(index)
}
pub fn get(&self, index: usize) -> Option<&str> {
if index >= self.entries.len() {
return None;
}
let entry = &self.entries[index];
let offset = O::to_usize(&entry.offset::<O>());
let length = entry.length();
if offset + length <= self.arena.len() {
let slice = &self.arena[offset..offset + length];
str::from_utf8(slice).ok()
} else {
None
}
}
pub fn get_bytes(&self, index: usize) -> Option<&[u8]> {
if index >= self.entries.len() {
return None;
}
let entry = &self.entries[index];
let offset = O::to_usize(&entry.offset::<O>());
let length = entry.length();
if offset + length <= self.arena.len() {
Some(&self.arena[offset..offset + length])
} else {
None
}
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn stats(&self) -> &BitPackedStats {
&self.stats
}
pub fn memory_info(&self) -> (usize, usize, f64) {
let arena_bytes = self.arena.len();
let entries_bytes = self.entries.len() * mem::size_of::<BitPackedEntry<T>>();
let overhead_bytes = mem::size_of::<Self>();
let total_bytes = arena_bytes + entries_bytes + overhead_bytes;
let vec_string_bytes = self.stats.total_strings * mem::size_of::<String>() +
arena_bytes + self.stats.total_strings * 8;
let ratio = if vec_string_bytes > 0 {
total_bytes as f64 / vec_string_bytes as f64
} else {
1.0
};
(total_bytes, vec_string_bytes, ratio)
}
pub fn has_hardware_acceleration(&self) -> bool {
self.config.enable_hardware_acceleration && cfg!(target_feature = "bmi2")
}
pub fn offset_type_info(&self) -> (&'static str, usize, usize) {
(O::type_name(), mem::size_of::<T>(), O::max_offset())
}
pub fn extend<I, S>(&mut self, iter: I) -> Result<Vec<usize>>
where
I: Iterator<Item = S>,
S: AsRef<str>,
{
let mut indices = Vec::new();
for s in iter {
indices.push(self.push(s.as_ref())?);
}
Ok(indices)
}
#[cfg(feature = "simd")]
pub fn find_simd(&self, needle: &str) -> Option<usize> {
if needle.len() < 16 {
return self.find_linear(needle);
}
self.find_simd_avx2(needle)
}
#[cfg(not(feature = "simd"))]
pub fn find(&self, needle: &str) -> Option<usize> {
self.find_linear(needle)
}
fn update_stats(&mut self) {
self.stats.total_strings = self.entries.len();
self.stats.arena_bytes_used = self.arena.len();
self.stats.index_bytes_used = self.entries.len() * mem::size_of::<BitPackedEntry<T>>();
if self.stats.total_strings > 0 {
self.stats.average_string_length =
self.stats.arena_bytes_used as f64 / self.stats.total_strings as f64;
}
let (our_bytes, vec_string_bytes, _) = self.memory_info();
if vec_string_bytes > 0 {
self.stats.memory_savings_percent =
(1.0 - our_bytes as f64 / vec_string_bytes as f64) * 100.0;
}
}
fn find_linear(&self, needle: &str) -> Option<usize> {
for i in 0..self.len() {
if let Some(s) = self.get(i) {
if s == needle {
return Some(i);
}
}
}
None
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
fn find_simd_avx2(&self, needle: &str) -> Option<usize> {
if !is_x86_feature_detected!("avx2") {
return self.find_linear(needle);
}
let needle_bytes = needle.as_bytes();
let needle_len = needle_bytes.len();
for i in 0..self.len() {
if let Some(candidate_bytes) = self.get_bytes(i) {
if candidate_bytes.len() == needle_len {
if unsafe { self.simd_compare_bytes(candidate_bytes, needle_bytes) } {
return Some(i);
}
}
}
}
None
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
unsafe fn simd_compare_bytes(&self, a: &[u8], b: &[u8]) -> bool {
use std::arch::x86_64::*;
if a.len() != b.len() {
return false;
}
let len = a.len();
let chunks = len / 32;
for i in 0..chunks {
let offset = i * 32;
unsafe {
let a_vec = _mm256_loadu_si256(a.as_ptr().add(offset) as *const _);
let b_vec = _mm256_loadu_si256(b.as_ptr().add(offset) as *const _);
let cmp = _mm256_cmpeq_epi8(a_vec, b_vec);
let mask = _mm256_movemask_epi8(cmp);
if mask != -1 {
return false;
}
}
}
for i in (chunks * 32)..len {
if a[i] != b[i] {
return false;
}
}
true
}
}
impl<T, O> Default for BitPackedStringVec<T, O>
where
T: Copy + std::fmt::Debug,
O: OffsetOps<T>,
{
fn default() -> Self {
Self::new()
}
}
impl<T, O> Clone for BitPackedStringVec<T, O>
where
T: Copy + std::fmt::Debug,
O: OffsetOps<T>,
{
fn clone(&self) -> Self {
Self {
arena: self.arena.clone(),
entries: self.entries.clone(),
config: self.config.clone(),
stats: self.stats.clone(),
_offset_ops: PhantomData,
}
}
}
pub struct BitPackedStringIter<'a, T, O>
where
T: Copy + std::fmt::Debug,
O: OffsetOps<T>,
{
vec: &'a BitPackedStringVec<T, O>,
current: usize,
}
impl<'a, T, O> Iterator for BitPackedStringIter<'a, T, O>
where
T: Copy + std::fmt::Debug,
O: OffsetOps<T>,
{
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.vec.len() {
let result = self.vec.get(self.current);
self.current += 1;
result
} else {
None
}
}
}
impl<T, O> BitPackedStringVec<T, O>
where
T: Copy + std::fmt::Debug,
O: OffsetOps<T>,
{
pub fn iter(&self) -> BitPackedStringIter<T, O> {
BitPackedStringIter { vec: self, current: 0 }
}
}
pub type BitPackedStringVec32 = BitPackedStringVec<u32, U32OffsetOps>;
pub type BitPackedStringVec64 = BitPackedStringVec<u64, U64OffsetOps>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bit_packed_entry_u32() {
let entry = BitPackedEntry::<u32>::new::<U32OffsetOps>(0x12345678, 0x9ABCDEF0).unwrap();
assert_eq!(U32OffsetOps::to_usize(&entry.offset::<U32OffsetOps>()), 0x12345678);
assert_eq!(entry.length(), 0x9ABCDEF0);
}
#[test]
fn test_bit_packed_entry_u64() {
let entry = BitPackedEntry::<u64>::new::<U64OffsetOps>(0x12345678AB, 0x123456).unwrap();
assert_eq!(U64OffsetOps::to_usize(&entry.offset::<U64OffsetOps>()), 0x12345678AB);
assert_eq!(entry.length(), 0x123456);
}
#[test]
fn test_u32_vector_basic_operations() {
let mut vec: BitPackedStringVec32 = BitPackedStringVec::new();
let idx1 = vec.push("hello").unwrap();
let idx2 = vec.push("world").unwrap();
assert_eq!(vec.get(idx1), Some("hello"));
assert_eq!(vec.get(idx2), Some("world"));
assert_eq!(vec.len(), 2);
}
#[test]
fn test_u64_vector_basic_operations() {
let mut vec: BitPackedStringVec64 = BitPackedStringVec::new();
let idx1 = vec.push("hello").unwrap();
let idx2 = vec.push("world").unwrap();
assert_eq!(vec.get(idx1), Some("hello"));
assert_eq!(vec.get(idx2), Some("world"));
assert_eq!(vec.len(), 2);
}
#[test]
fn test_memory_efficiency_u32() {
let mut vec: BitPackedStringVec32 = BitPackedStringVec::with_capacity(1000);
for i in 0..1000 {
vec.push(&format!("string_{:04}", i)).unwrap();
}
let (our_bytes, vec_string_bytes, ratio) = vec.memory_info();
println!("BitPackedStringVec<u32> memory test:");
println!(" Our size: {} bytes", our_bytes);
println!(" Vec<String> equivalent: {} bytes", vec_string_bytes);
println!(" Memory ratio: {:.3}", ratio);
println!(" Memory savings: {:.1}%", (1.0 - ratio) * 100.0);
assert!(our_bytes < vec_string_bytes);
assert!(ratio < 0.7); }
#[test]
fn test_memory_efficiency_u64() {
let mut vec: BitPackedStringVec64 = BitPackedStringVec::with_capacity(1000);
for i in 0..1000 {
vec.push(&format!("string_{:04}", i)).unwrap();
}
let (our_bytes, vec_string_bytes, ratio) = vec.memory_info();
println!("BitPackedStringVec<u64> memory test:");
println!(" Our size: {} bytes", our_bytes);
println!(" Vec<String> equivalent: {} bytes", vec_string_bytes);
println!(" Memory ratio: {:.3}", ratio);
println!(" Memory savings: {:.1}%", (1.0 - ratio) * 100.0);
assert!(our_bytes < vec_string_bytes);
assert!(ratio < 0.8); }
#[test]
fn test_hardware_acceleration_detection() {
let vec: BitPackedStringVec32 = BitPackedStringVec::new();
let has_accel = vec.has_hardware_acceleration();
println!("Hardware acceleration available: {}", has_accel);
assert_eq!(has_accel, cfg!(target_feature = "bmi2"));
}
#[test]
fn test_configuration_presets() {
let perf_config = BitPackedConfig::performance_optimized();
assert_eq!(perf_config.simd_alignment, 32);
assert!(perf_config.enable_hardware_acceleration);
let mem_config = BitPackedConfig::memory_optimized();
assert_eq!(mem_config.simd_alignment, 16);
let large_config = BitPackedConfig::large_dataset();
assert_eq!(large_config.simd_alignment, 64);
assert!(large_config.use_memory_mapping);
}
#[test]
fn test_offset_type_info() {
let vec32: BitPackedStringVec32 = BitPackedStringVec::new();
let (name, size, max_offset) = vec32.offset_type_info();
assert_eq!(name, "u32");
assert_eq!(size, 4);
assert_eq!(max_offset, u32::MAX as usize);
let vec64: BitPackedStringVec64 = BitPackedStringVec::new();
let (name, size, max_offset) = vec64.offset_type_info();
assert_eq!(name, "u64");
assert_eq!(size, 8);
assert_eq!(max_offset, usize::MAX);
}
#[test]
fn test_bulk_operations() {
let mut vec: BitPackedStringVec32 = BitPackedStringVec::new();
let strings = vec!["first", "second", "third", "fourth"];
let indices = vec.extend(strings.iter()).unwrap();
assert_eq!(indices.len(), 4);
assert_eq!(vec.len(), 4);
for (i, &s) in strings.iter().enumerate() {
assert_eq!(vec.get(indices[i]), Some(s));
}
}
#[test]
fn test_iterator() {
let mut vec: BitPackedStringVec32 = BitPackedStringVec::new();
vec.push("first").unwrap();
vec.push("second").unwrap();
vec.push("third").unwrap();
let collected: Vec<&str> = vec.iter().collect();
assert_eq!(collected, vec!["first", "second", "third"]);
}
#[test]
fn test_large_strings() {
let mut vec: BitPackedStringVec64 = BitPackedStringVec::new();
let large_string = "x".repeat(1024 * 1024); let idx = vec.push(&large_string).unwrap();
assert_eq!(vec.get(idx), Some(large_string.as_str()));
let stats = vec.stats();
println!("Large string test stats:");
println!(" Arena bytes: {}", stats.arena_bytes_used);
println!(" Average string length: {:.1}", stats.average_string_length);
println!(" Memory savings: {:.1}%", stats.memory_savings_percent);
}
#[cfg(feature = "simd")]
#[test]
fn test_simd_search() {
let mut vec: BitPackedStringVec32 = BitPackedStringVec::new();
vec.push("hello world").unwrap();
vec.push("goodbye world").unwrap();
vec.push("hello rust").unwrap();
assert_eq!(vec.find_simd("hello world"), Some(0));
assert_eq!(vec.find_simd("goodbye world"), Some(1));
assert_eq!(vec.find_simd("hello rust"), Some(2));
assert_eq!(vec.find_simd("not found"), None);
}
}