use std::cmp::max;
use std::collections::TryReserveError;
use std::mem;
use crate::segment::common::vector_utils::{TrySetCapacity, TrySetCapacityExact};
use crate::segment::vector_storage::VectorOffsetType;
use crate::segment::vector_storage::common::CHUNK_SIZE;
#[derive(Debug)]
pub struct VolatileChunkedVectors<T> {
dim: usize,
len: usize,
chunk_capacity: usize,
chunks: Vec<Vec<T>>,
}
impl<T: Copy + Clone + Default> VolatileChunkedVectors<T> {
pub fn new(dim: usize) -> Self {
assert_ne!(dim, 0, "The vector's dimension cannot be 0");
let vector_size = dim * mem::size_of::<T>();
let chunk_capacity = CHUNK_SIZE / vector_size;
assert_ne!(chunk_capacity, 0, "The vector's size is too big");
Self {
dim,
len: 0,
chunk_capacity,
chunks: Vec::new(),
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn shrink_last_chunk(&mut self) {
if let Some(last) = self.chunks.last_mut() {
last.shrink_to_fit();
}
}
pub fn heap_size_bytes(&self) -> usize {
let Self {
dim: _,
len: _,
chunk_capacity: _,
chunks,
} = self;
chunks.capacity() * mem::size_of::<Vec<T>>()
+ chunks
.iter()
.map(|chunk| chunk.capacity() * mem::size_of::<T>())
.sum::<usize>()
}
pub fn get(&self, key: VectorOffsetType) -> &[T] {
self.get_opt(key).expect("vector not found")
}
pub fn get_opt(&self, key: VectorOffsetType) -> Option<&[T]> {
if self.chunks.is_empty() {
return None;
}
self.chunks
.get(key / self.chunk_capacity)
.and_then(|chunk_data| {
let idx = (key % self.chunk_capacity) * self.dim;
let range = idx..idx + self.dim;
chunk_data.get(range)
})
}
pub fn get_many(&self, key: VectorOffsetType, count: usize) -> Option<&[T]> {
if self.chunks.is_empty() {
return None;
}
self.chunks
.get(key / self.chunk_capacity)
.and_then(|chunk_data| {
let idx = (key % self.chunk_capacity) * self.dim;
let range = idx..idx + count * self.dim;
chunk_data.get(range)
})
}
pub fn push(&mut self, vector: &[T]) -> Result<VectorOffsetType, TryReserveError> {
let new_id = self.len;
self.insert(new_id, vector)?;
Ok(new_id)
}
pub fn get_chunk_left_keys(&self, start_key: VectorOffsetType) -> usize {
self.chunk_capacity - (start_key % self.chunk_capacity)
}
pub fn insert(&mut self, key: VectorOffsetType, vector: &[T]) -> Result<(), TryReserveError> {
assert_eq!(vector.len(), self.dim, "Vector size mismatch");
self.insert_many(key, vector, 1)
}
pub fn insert_many(
&mut self,
key: VectorOffsetType,
vectors: &[T],
vectors_count: usize,
) -> Result<(), TryReserveError> {
assert_eq!(
vectors.len(),
vectors_count * self.dim,
"Vector size mismatch"
);
assert!(
self.get_chunk_left_keys(key) >= vectors_count,
"Index out of bounds"
);
let desired_capacity = self.chunk_capacity * self.dim;
let new_len = max(self.len, key + vectors_count);
let chunks_len = new_len.div_ceil(self.chunk_capacity);
if chunks_len > self.chunks.len() {
if let Some(last_chunk) = self.chunks.last_mut() {
last_chunk.try_set_capacity_exact(desired_capacity)?;
last_chunk.resize_with(desired_capacity, T::default);
}
self.chunks.try_set_capacity(chunks_len)?;
let new_chunks = chunks_len - self.chunks.len();
let skipped_chunks = new_chunks - 1;
for _ in 0..skipped_chunks {
let mut chunk = Vec::new();
chunk.try_set_capacity_exact(desired_capacity)?;
chunk.resize_with(desired_capacity, T::default);
self.chunks.push(chunk);
}
self.chunks.push(Default::default());
assert_eq!(self.chunks.len(), chunks_len);
}
let chunk_idx = key / self.chunk_capacity;
let chunk_data = &mut self.chunks[chunk_idx];
let idx = (key % self.chunk_capacity) * self.dim;
if chunk_data.len() < idx + vectors.len() {
if chunk_idx != 0 {
chunk_data.try_set_capacity_exact(desired_capacity)?;
}
chunk_data.resize_with(idx + vectors.len(), T::default);
}
let data = &mut chunk_data[idx..idx + vectors.len()];
data.copy_from_slice(vectors);
self.len = new_len;
Ok(())
}
}
impl<T: Clone> TrySetCapacityExact for VolatileChunkedVectors<T> {
fn try_set_capacity_exact(&mut self, capacity: usize) -> Result<(), TryReserveError> {
let num_chunks = capacity.div_ceil(self.chunk_capacity);
let last_chunk_idx = num_chunks.saturating_sub(1);
self.chunks.try_set_capacity_exact(num_chunks)?;
self.chunks.resize_with(num_chunks, Vec::new);
for chunk_idx in 0..num_chunks {
if chunk_idx == last_chunk_idx {
let remainder = capacity % self.chunk_capacity;
let desired_capacity = if remainder == 0 {
self.chunk_capacity * self.dim
} else {
remainder * self.dim
};
self.chunks[chunk_idx].try_set_capacity_exact(desired_capacity)?;
} else {
let desired_capacity = self.chunk_capacity * self.dim;
self.chunks[chunk_idx].try_set_capacity_exact(desired_capacity)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::mem;
use super::*;
use crate::segment::vector_storage::common::CHUNK_SIZE;
#[test]
fn test_chunked_vectors_with_skipped_chunks() {
let mut vectors = VolatileChunkedVectors::new(3);
assert_eq!(vectors.get_opt(0), None);
vectors.insert(0, &[1, 2, 3]).unwrap();
vectors.insert(10_000_000, &[4, 5, 6]).unwrap();
assert!(vectors.chunks.len() > 3);
assert_eq!(vectors.get(0), &[1, 2, 3]);
assert_eq!(vectors.get(10_000_000), &[4, 5, 6]);
assert_eq!(vectors.get_opt(10_000_001), None);
assert_eq!(vectors.get(100), &[0, 0, 0]);
assert_eq!(vectors.get(5_000_000), &[0, 0, 0]);
}
#[test]
fn test_try_set_capacity_exact_zero_does_not_panic() {
let mut vectors = VolatileChunkedVectors::<u8>::new(3);
vectors.try_set_capacity_exact(0).unwrap();
assert!(vectors.chunks.is_empty());
assert_eq!(vectors.len(), 0);
}
#[test]
fn try_set_capacity_exact_exact_multiple_of_chunk_reserves_full_last_chunk() {
let dim = 3;
let chunk_capacity = CHUNK_SIZE / (dim * mem::size_of::<u8>());
let mut vectors = VolatileChunkedVectors::<u8>::new(dim);
vectors
.try_set_capacity_exact(chunk_capacity)
.expect("single full chunk");
assert_eq!(vectors.chunks.len(), 1);
assert_eq!(vectors.chunks[0].capacity(), chunk_capacity * dim);
vectors
.try_set_capacity_exact(2 * chunk_capacity)
.expect("two full chunks");
assert_eq!(vectors.chunks.len(), 2);
assert_eq!(vectors.chunks[0].capacity(), chunk_capacity * dim);
assert_eq!(vectors.chunks[1].capacity(), chunk_capacity * dim);
}
}