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};