Skip to main content

velesdb_core/
perf_optimizations.rs

1//! Performance optimizations module for ultra-fast vector operations.
2//!
3//! This module provides:
4//! - **Contiguous vector storage**: Cache-friendly memory layout
5//! - **Prefetch hints**: CPU cache warming for HNSW traversal
6//! - **Batch distance computation**: SIMD-optimized batch operations
7//!
8//! # Performance Targets
9//!
10//! - Bulk import: 50K+ vectors/sec at 768D
11//! - Search latency: < 1ms for 1M vectors
12//! - Memory efficiency: 50% reduction with FP16
13//!
14//! # Safety (EPIC-032/US-002)
15//!
16//! `ContiguousVectors` uses `NonNull<f32>` to encode non-nullness at the type level,
17//! eliminating null pointer checks and making invariants explicit. Memory is managed
18//! via RAII with `AllocGuard` for panic-safe resize operations.
19
20use crate::validation::{validate_dimension, validate_dimension_match};
21use std::alloc::{alloc_zeroed, Layout};
22use std::fmt;
23use std::ptr::{self, NonNull};
24
25// =============================================================================
26// Contiguous Vector Storage (Cache-Optimized)
27// =============================================================================
28
29/// Contiguous memory layout for vectors (cache-friendly).
30///
31/// Stores all vectors in a single contiguous buffer to maximize
32/// cache locality and enable SIMD prefetching.
33///
34/// # Memory Layout
35///
36/// ```text
37/// [v0_d0, v0_d1, ..., v0_dn, v1_d0, v1_d1, ..., v1_dn, ...]
38/// ```
39///
40/// # Safety Invariants (EPIC-032/US-002)
41///
42/// - `data` is always non-null (enforced by `NonNull`)
43/// - `data` points to memory allocated with 64-byte alignment
44/// - `capacity * dimension * sizeof(f32)` bytes are always allocated
45/// - `count <= capacity` is always maintained
46pub struct ContiguousVectors {
47    /// Non-null contiguous data buffer (EPIC-032/US-002: type-level non-null guarantee)
48    pub(crate) data: NonNull<f32>,
49    /// Vector dimension
50    pub(crate) dimension: usize,
51    /// Number of vectors stored
52    pub(crate) count: usize,
53    /// Allocated capacity (number of vectors)
54    pub(crate) capacity: usize,
55}
56
57// SAFETY: `ContiguousVectors` is `Send` because it owns its allocation.
58// - Condition 1: The backing buffer is uniquely owned by the struct.
59// - Condition 2: Mutation requires `&mut self` or lock-guarded interior access.
60// SAFETY: Moving ownership of this container between threads is sound.
61unsafe impl Send for ContiguousVectors {}
62// SAFETY: `ContiguousVectors` is `Sync` because shared access is read-only.
63// - Condition 1: All writes happen through methods requiring mutable or exclusive lock access.
64// - Condition 2: Returned shared slices borrow immutably and cannot mutate internal state.
65// SAFETY: Concurrent shared references cannot violate aliasing rules.
66unsafe impl Sync for ContiguousVectors {}
67
68impl fmt::Debug for ContiguousVectors {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        f.debug_struct("ContiguousVectors")
71            .field("dimension", &self.dimension)
72            .field("count", &self.count)
73            .field("capacity", &self.capacity)
74            .finish_non_exhaustive()
75    }
76}
77
78impl ContiguousVectors {
79    /// Creates a new `ContiguousVectors` with the given dimension and initial capacity.
80    ///
81    /// # Arguments
82    ///
83    /// * `dimension` - Vector dimension (must be > 0)
84    /// * `capacity` - Initial capacity (number of vectors)
85    ///
86    /// # Errors
87    ///
88    /// Returns [`Error::InvalidDimension`] if `dimension` is 0 or exceeds
89    /// [`MAX_DIMENSION`](crate::validation::MAX_DIMENSION).
90    /// Returns [`Error::AllocationFailed`] if memory allocation fails or exceeds
91    /// the [`AllocGuard`](crate::alloc_guard::AllocGuard) ceiling.
92    ///
93    /// [`Error::InvalidDimension`]: crate::error::Error::InvalidDimension
94    /// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
95    #[allow(clippy::cast_ptr_alignment)] // Layout is 64-byte aligned
96    pub fn new(dimension: usize, capacity: usize) -> crate::error::Result<Self> {
97        // Enforce the advertised dimension range (#899): previously `max: 65_536`
98        // was reported in errors but never validated, so an oversized dimension
99        // could drive `dimension * capacity` products toward overflow.
100        validate_dimension(dimension)?;
101
102        let capacity = capacity.max(16); // Minimum 16 vectors
103
104        // Reject pathological/attacker-sized allocations before touching the
105        // allocator (#899). Legitimate large indexes stay well under the ceiling.
106        crate::alloc_guard::check_alloc_bound(Self::byte_size(dimension, capacity)?)?;
107        let layout = Self::layout(dimension, capacity)?;
108
109        // SAFETY: `alloc_zeroed` requires a valid non-zero layout.
110        // - Condition 1: `dimension > 0` and `capacity >= 16` guarantee non-zero size.
111        // - Condition 2: `layout` is built via `Layout::from_size_align` and therefore valid.
112        // SAFETY: Zero-initialized allocation guarantees all f32 slots are 0.0,
113        // preventing UB when `insert_at` creates sparse gaps (indices 0..N not all written).
114        let ptr = unsafe { alloc_zeroed(layout) };
115
116        // EPIC-032/US-002: Use NonNull for type-level non-null guarantee
117        let data = NonNull::new(ptr.cast::<f32>()).ok_or_else(|| {
118            crate::error::Error::AllocationFailed(
119                "ContiguousVectors: allocator returned null".to_string(),
120            )
121        })?;
122
123        Ok(Self {
124            data,
125            dimension,
126            count: 0,
127            capacity,
128        })
129    }
130
131    /// Returns the buffer size in bytes for `dimension * capacity` f32s.
132    ///
133    /// # Errors
134    ///
135    /// Returns [`Error::AllocationFailed`] if `dimension * capacity * 4` overflows
136    /// `usize`.
137    ///
138    /// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
139    pub(crate) fn byte_size(dimension: usize, capacity: usize) -> crate::error::Result<usize> {
140        dimension
141            .checked_mul(capacity)
142            .and_then(|s| s.checked_mul(std::mem::size_of::<f32>()))
143            .ok_or_else(|| {
144                crate::error::Error::AllocationFailed(format!(
145                    "Size overflow: {dimension} * {capacity} * {}",
146                    std::mem::size_of::<f32>()
147                ))
148            })
149    }
150
151    /// Returns the memory layout for the given dimension and capacity.
152    ///
153    /// # Errors
154    ///
155    /// Returns [`Error::AllocationFailed`] if the layout parameters are invalid.
156    ///
157    /// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
158    pub(crate) fn layout(dimension: usize, capacity: usize) -> crate::error::Result<Layout> {
159        let size = Self::byte_size(dimension, capacity)?;
160        let align = 64; // Cache line alignment for optimal prefetch
161        Layout::from_size_align(size.max(64), align)
162            .map_err(|e| crate::error::Error::AllocationFailed(format!("Invalid layout: {e}")))
163    }
164
165    /// Returns the dimension of stored vectors.
166    #[inline]
167    #[must_use]
168    pub const fn dimension(&self) -> usize {
169        self.dimension
170    }
171
172    /// Returns the number of vectors stored.
173    #[inline]
174    #[must_use]
175    pub const fn len(&self) -> usize {
176        self.count
177    }
178
179    /// Returns true if no vectors are stored.
180    #[inline]
181    #[must_use]
182    pub const fn is_empty(&self) -> bool {
183        self.count == 0
184    }
185
186    /// Returns the capacity (max vectors before reallocation).
187    #[inline]
188    #[must_use]
189    pub const fn capacity(&self) -> usize {
190        self.capacity
191    }
192
193    /// Returns the raw contiguous buffer as a flat slice.
194    ///
195    /// The slice contains all vectors packed sequentially:
196    /// `[v0_d0, v0_d1, ..., v1_d0, ...]`.
197    /// Useful for GPU upload without copying.
198    #[inline]
199    #[must_use]
200    pub fn as_flat_slice(&self) -> &[f32] {
201        if self.count == 0 {
202            return &[];
203        }
204        // `count <= capacity` and `capacity * dimension` was validated to fit in
205        // `usize` at allocation time, so this product cannot overflow here.
206        let total = self.count.saturating_mul(self.dimension);
207        // SAFETY: All `capacity * dimension` f32s are valid because both initial allocation
208        // (`alloc_zeroed`) and resize (`AllocGuard::new_zeroed`) zero-initialize the buffer.
209        // `count * dimension <= capacity * dimension`, `data` is non-null per `NonNull`
210        // invariant. Even sparse `insert_at` gaps contain valid 0.0 f32 values.
211        // - Condition 1: `data` is a valid, aligned `NonNull<f32>` pointer.
212        // - Condition 2: `total <= capacity * dimension` ensures the slice is within the allocation.
213        // - Condition 3: All bytes in the allocation are initialized (zeroed or written).
214        // SAFETY: Zero-copy GPU upload requires a contiguous &[f32] view.
215        unsafe { std::slice::from_raw_parts(self.data.as_ptr(), total) }
216    }
217
218    /// Gathers vectors at the specified indices into a contiguous flat buffer.
219    ///
220    /// Returns a new `Vec<f32>` containing the selected vectors packed sequentially.
221    /// Useful for GPU upload when only a subset of vectors is needed (e.g., reranking).
222    ///
223    /// # Important
224    ///
225    /// Out-of-bounds indices are silently skipped — the result may contain fewer
226    /// vectors than `indices.len()`. Callers **must** validate
227    /// `result.len() == indices.len() * dimension` before using the result in
228    /// positional operations (e.g., `zip` with an ID map), otherwise scores
229    /// will be misattributed to wrong IDs.
230    #[must_use]
231    pub fn gather_flat(&self, indices: &[usize]) -> Vec<f32> {
232        // Saturating reservation hint: an overflowing `indices.len() * dimension`
233        // would otherwise panic inside `Vec::with_capacity`. `extend_from_slice`
234        // grows on demand, so a clamped hint stays correct (#899).
235        let mut result = Vec::with_capacity(indices.len().saturating_mul(self.dimension));
236        for &idx in indices {
237            if let Some(vec) = self.get(idx) {
238                result.extend_from_slice(vec);
239            }
240        }
241        result
242    }
243
244    /// Returns total memory usage in bytes.
245    ///
246    /// Saturates instead of overflowing; `capacity * dimension * 4` was validated
247    /// to fit in `usize` at allocation time, so saturation is unreachable for a
248    /// live buffer and exists purely as a defensive backstop (#899).
249    #[inline]
250    #[must_use]
251    pub const fn memory_bytes(&self) -> usize {
252        self.capacity
253            .saturating_mul(self.dimension)
254            .saturating_mul(std::mem::size_of::<f32>())
255    }
256
257    /// Inserts a vector at a specific index.
258    ///
259    /// Automatically grows capacity if needed.
260    /// Note: This allows sparse population. Uninitialized slots contain undefined
261    /// data (or 0.0 if alloc gave zeroed memory).
262    ///
263    /// # Errors
264    ///
265    /// Returns [`crate::error::Error::DimensionMismatch`] if `vector.len() != self.dimension`.
266    /// Returns [`Error::AllocationFailed`] if capacity growth fails.
267    ///
268    /// [`crate::error::Error::DimensionMismatch`]: crate::error::Error::DimensionMismatch
269    /// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
270    pub fn insert_at(&mut self, index: usize, vector: &[f32]) -> crate::error::Result<()> {
271        validate_dimension_match(self.dimension, vector.len())?;
272
273        // #899: `index == usize::MAX` made `index + 1` wrap to 0, so capacity was
274        // never grown and `index * dimension` overflowed, producing an OOB
275        // `copy_nonoverlapping` write in release builds. Reject overflow instead.
276        let required = index.checked_add(1).ok_or_else(|| {
277            crate::error::Error::AllocationFailed(format!(
278                "insert_at: index {index} + 1 overflows usize"
279            ))
280        })?;
281        self.ensure_capacity(required)?;
282
283        let offset = index.checked_mul(self.dimension).ok_or_else(|| {
284            crate::error::Error::AllocationFailed(format!(
285                "insert_at: offset {index} * {} overflows usize",
286                self.dimension
287            ))
288        })?;
289        // SAFETY: We ensured capacity covers index, data is non-null (NonNull invariant)
290        // - Condition 1: Capacity was verified to cover the target index.
291        // - Condition 2: Both source and destination pointers are valid and properly aligned.
292        // SAFETY: Efficient bulk memory copy for vector insertion.
293        unsafe {
294            ptr::copy_nonoverlapping(
295                vector.as_ptr(),
296                self.data.as_ptr().add(offset),
297                self.dimension,
298            );
299        }
300
301        // Update count if we're extending the "used" range.
302        // `required == index + 1` (checked above), so this cannot overflow.
303        if index >= self.count {
304            self.count = required;
305        }
306        Ok(())
307    }
308
309    /// Adds a vector to the storage.
310    ///
311    /// # Errors
312    ///
313    /// Returns [`crate::error::Error::DimensionMismatch`] if `vector.len() != self.dimension`.
314    /// Returns [`Error::AllocationFailed`] if capacity growth fails.
315    ///
316    /// [`crate::error::Error::DimensionMismatch`]: crate::error::Error::DimensionMismatch
317    /// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
318    pub fn push(&mut self, vector: &[f32]) -> crate::error::Result<()> {
319        self.insert_at(self.count, vector)
320    }
321
322    /// Adds multiple vectors in batch (optimized).
323    ///
324    /// # Arguments
325    ///
326    /// * `vectors` - Iterator of vectors to add
327    ///
328    /// # Returns
329    ///
330    /// Number of vectors added.
331    ///
332    /// # Errors
333    ///
334    /// Returns [`crate::error::Error::DimensionMismatch`] or [`Error::AllocationFailed`] on the
335    /// first vector that fails. Vectors added before the failure remain in storage.
336    ///
337    /// [`crate::error::Error::DimensionMismatch`]: crate::error::Error::DimensionMismatch
338    /// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
339    pub fn push_batch(&mut self, vectors: &[&[f32]]) -> crate::error::Result<usize> {
340        if vectors.is_empty() {
341            return Ok(0);
342        }
343        // Validate all dimensions upfront to prevent partial writes on error.
344        for vector in vectors {
345            validate_dimension_match(self.dimension, vector.len())?;
346        }
347        let required = self.count.checked_add(vectors.len()).ok_or_else(|| {
348            crate::error::Error::AllocationFailed(format!(
349                "push_batch: count {} + {} overflows usize",
350                self.count,
351                vectors.len()
352            ))
353        })?;
354        self.ensure_capacity(required)?;
355        for vector in vectors {
356            // `offset < required * dimension`, and `required * dimension` was
357            // validated to fit in `usize` by `ensure_capacity`/`layout` above.
358            let offset = self.count.saturating_mul(self.dimension);
359            // SAFETY: ensure_capacity (called above) guarantees room for
360            // self.count + vectors.len() elements, and all dimensions were
361            // validated above so offset + dimension is within bounds.
362            // - Condition 1: offset + dimension is within allocated buffer.
363            // - Condition 2: Both pointers are valid and aligned for f32.
364            // - Condition 3: &mut self guarantees exclusive access — no data race.
365            // SAFETY: Batch push with single pre-allocation.
366            unsafe {
367                std::ptr::copy_nonoverlapping(
368                    vector.as_ptr(),
369                    self.data.as_ptr().add(offset),
370                    self.dimension,
371                );
372            }
373            self.count += 1;
374        }
375        Ok(vectors.len())
376    }
377
378    /// Gets a vector by index.
379    ///
380    /// # Returns
381    ///
382    /// Slice to the vector data, or `None` if index is out of bounds.
383    #[inline]
384    #[must_use]
385    pub fn get(&self, index: usize) -> Option<&[f32]> {
386        if index >= self.count {
387            // Note: In sparse mode, index < count doesn't guarantee it was initialized,
388            // but for HNSW dense IDs it typically does.
389            return None;
390        }
391
392        let offset = index * self.dimension;
393        // SAFETY: Index is within bounds (checked against count, which is <= capacity)
394        // - Condition 1: index < count ensures access is within initialized range.
395        // - Condition 2: data is non-null per NonNull invariant.
396        // SAFETY: Zero-copy slice creation from contiguous storage.
397        Some(unsafe { std::slice::from_raw_parts(self.data.as_ptr().add(offset), self.dimension) })
398    }
399
400    /// Gets a vector by index (unchecked).
401    ///
402    /// # Safety
403    ///
404    /// Caller must ensure `index < self.len()`.
405    ///
406    /// # Debug Assertions
407    ///
408    /// In debug builds, this function will panic if `index >= self.len()`.
409    /// This catches bugs early during development without impacting release performance.
410    #[inline]
411    #[must_use]
412    pub unsafe fn get_unchecked(&self, index: usize) -> &[f32] {
413        debug_assert!(
414            index < self.count,
415            "index out of bounds: index={index}, count={}",
416            self.count
417        );
418        let offset = index * self.dimension;
419        // SAFETY: Caller guarantees index < count, data is non-null (NonNull invariant)
420        // - Condition 1: Caller contract ensures index < count.
421        // - Condition 2: data is non-null per NonNull invariant.
422        // SAFETY: Performance-critical path requiring unchecked access.
423        std::slice::from_raw_parts(self.data.as_ptr().add(offset), self.dimension)
424    }
425
426    /// Prefetches a vector into multiple cache levels for upcoming access.
427    ///
428    /// Uses cross-platform multi-cache-line prefetch (`x86_64` + `aarch64` + no-op fallback)
429    /// to warm CPU caches before SIMD distance computation.
430    #[inline]
431    pub fn prefetch(&self, index: usize) {
432        if index < self.count {
433            let offset = index * self.dimension;
434            // SAFETY: index < count implies offset is within allocated range,
435            // data is non-null per NonNull invariant.
436            // - Condition 1: Bounds check ensures offset + dimension <= capacity * dimension.
437            // - Condition 2: NonNull guarantees pointer validity.
438            // SAFETY: Create slice for cross-platform multi-cache-line prefetch.
439            let vector = unsafe {
440                std::slice::from_raw_parts(self.data.as_ptr().add(offset), self.dimension)
441            };
442            crate::simd_native::prefetch_vector_multi_cache_line(vector);
443        }
444    }
445}
446
447// Backward-compatible re-exports from contiguous_ops
448pub use crate::contiguous_ops::{
449    batch_cosine_similarities, batch_dot_products_simd, pad_to_simd_width,
450};