diff --git a/Cargo.toml b/Cargo.toml index 6e77c1b..64144f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ rand_distr = "0.4" sha2 = "0.10.7" # Added SHA-2 cryptographic hash functions sha3 = { version = "0.10", optional = true } blake3 = { version = "1", optional = true } -rand = "0.8" serde_bytes = "0.11" wide = "0.7" warp = "0.3" diff --git a/benches/vector_operations.rs b/benches/vector_operations.rs index 3cc4fc4..5254a6b 100644 --- a/benches/vector_operations.rs +++ b/benches/vector_operations.rs @@ -45,6 +45,33 @@ fn bench_vector_operations(c: &mut Criterion) { group.finish(); } + // Benchmark SIMD-enabled operations + { + let mut group = c.benchmark_group("vector_simd_operations"); + let simd_dims = vec![8, 32, 128]; + for &dim in &simd_dims { + let v1 = Vector::random(dim); + let v2 = Vector::random(dim); + + group.bench_with_input(BenchmarkId::new("dot_simd", dim), &dim, |b, _| { + b.iter(|| black_box(&v1).dot(black_box(&v2))) + }); + + group.bench_with_input(BenchmarkId::new("euclidean_simd", dim), &dim, |b, _| { + b.iter(|| black_box(&v1).euclidean_distance(black_box(&v2))) + }); + + group.bench_with_input(BenchmarkId::new("manhattan_simd", dim), &dim, |b, _| { + b.iter(|| black_box(&v1).manhattan_distance(black_box(&v2))) + }); + + group.bench_with_input(BenchmarkId::new("hamming_simd", dim), &dim, |b, _| { + b.iter(|| black_box(&v1).hamming_distance(black_box(&v2))) + }); + } + group.finish(); + } + // Benchmark batch operations { let mut group = c.benchmark_group("batch_operations"); diff --git a/src/core/centroid_crdt.rs b/src/core/centroid_crdt.rs index e2792c8..17d3226 100644 --- a/src/core/centroid_crdt.rs +++ b/src/core/centroid_crdt.rs @@ -1,12 +1,8 @@ -use crate::core::centroid::Centroid; -use crate::core::vector::Vector; -use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; -<<<<<<< Updated upstream -use thiserror::Error; -======= ->>>>>>> Stashed changes +use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::core::centroid::Centroid; +use crate::core::vector::Vector; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CentroidOperation { @@ -23,15 +19,6 @@ pub enum OperationType { Delete, } -#[derive(Debug, Error)] -pub enum CentroidCRDTError { - #[error("Centroid with ID {0} not found")] - NotFound(Uuid), - - #[error("Invalid distance value encountered during comparison")] - InvalidDistance, -} - #[derive(Debug, Clone)] pub struct CentroidCRDT { node_id: Uuid, @@ -66,17 +53,9 @@ impl CentroidCRDT { centroid_id } -<<<<<<< Updated upstream - pub fn update_centroid( - &mut self, - centroid_id: Uuid, - vector: Vector, - ) -> Result<(), CentroidCRDTError> { -======= pub fn update_centroid(&mut self, centroid_id: Uuid, vector: Vector) -> Result<(), String> { ->>>>>>> Stashed changes if !self.centroids.contains_key(¢roid_id) { - return Err(CentroidCRDTError::NotFound(centroid_id)); + return Err(format!("Centroid with ID {} not found", centroid_id)); } let operation = CentroidOperation { @@ -91,13 +70,9 @@ impl CentroidCRDT { Ok(()) } -<<<<<<< Updated upstream - pub fn delete_centroid(&mut self, centroid_id: Uuid) -> Result<(), CentroidCRDTError> { -======= pub fn delete_centroid(&mut self, centroid_id: Uuid) -> Result<(), String> { ->>>>>>> Stashed changes if !self.centroids.contains_key(¢roid_id) { - return Err(CentroidCRDTError::NotFound(centroid_id)); + return Err(format!("Centroid with ID {} not found", centroid_id)); } let operation = CentroidOperation { @@ -120,12 +95,11 @@ impl CentroidCRDT { match &operation.operation_type { OperationType::Create(vector) => { // Only create if it doesn't exist or if this is newer than the existing centroid - let should_create = - if let Some(existing) = self.centroids.get(&operation.centroid_id) { - operation.timestamp > existing.updated_at - } else { - true - }; + let should_create = if let Some(existing) = self.centroids.get(&operation.centroid_id) { + operation.timestamp > existing.updated_at + } else { + true + }; if should_create { let now = chrono::Utc::now(); @@ -138,7 +112,7 @@ impl CentroidCRDT { }; self.centroids.insert(operation.centroid_id, centroid); } - } + }, OperationType::Update(vector) => { if let Some(centroid) = self.centroids.get_mut(&operation.centroid_id) { if operation.timestamp > centroid.updated_at { @@ -146,14 +120,14 @@ impl CentroidCRDT { centroid.updated_at = operation.timestamp; } } - } + }, OperationType::Delete => { if let Some(centroid) = self.centroids.get(&operation.centroid_id) { if operation.timestamp > centroid.updated_at { self.centroids.remove(&operation.centroid_id); } } - } + }, } let op_id = operation.id; @@ -177,32 +151,15 @@ impl CentroidCRDT { self.centroids.values().collect() } -<<<<<<< Updated upstream - pub fn find_nearest( - &self, - vector: &Vector, - limit: usize, - ) -> Result, CentroidCRDTError> { -======= pub fn find_nearest(&self, vector: &Vector, limit: usize) -> Vec<(&Centroid, f32)> { ->>>>>>> Stashed changes - let mut distances: Vec<(&Centroid, f32)> = self - .centroids + let mut distances: Vec<(&Centroid, f32)> = self.centroids .values() .map(|c| (c, c.distance_to(vector))) .collect(); -<<<<<<< Updated upstream - if distances.iter().any(|(_, d)| !d.is_finite()) { - return Err(CentroidCRDTError::InvalidDistance); - } - - distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)); -======= distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); ->>>>>>> Stashed changes distances.truncate(limit); - Ok(distances) + distances } } @@ -235,12 +192,7 @@ mod tests { let centroid_id = crdt.create_centroid(vector1); let vector2 = Vector::new(vec![4.0, 5.0, 6.0]); -<<<<<<< Updated upstream - let res = crdt.update_centroid(centroid_id, vector2); - assert!(res.is_ok()); -======= crdt.update_centroid(centroid_id, vector2).unwrap(); ->>>>>>> Stashed changes assert_eq!(crdt.operations.len(), 2); @@ -263,12 +215,7 @@ mod tests { let vector = Vector::new(vec![1.0, 2.0, 3.0]); let centroid_id = crdt.create_centroid(vector.clone()); -<<<<<<< Updated upstream - let res = crdt.delete_centroid(centroid_id); - assert!(res.is_ok()); -======= crdt.delete_centroid(centroid_id).unwrap(); ->>>>>>> Stashed changes assert_eq!(crdt.centroids.len(), 0); assert_eq!(crdt.operations.len(), 2); @@ -354,4 +301,4 @@ mod tests { let centroid = crdt1.get_centroid(¢roid_id).unwrap(); assert_eq!(centroid.vector.values, vector2.values); } -} +} \ No newline at end of file diff --git a/src/core/vector.rs b/src/core/vector.rs index c39a05a..0f9dd87 100644 --- a/src/core/vector.rs +++ b/src/core/vector.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use std::ops::{Add, Div, Mul, Sub}; +use wide::f32x4; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Vector { @@ -8,6 +9,7 @@ pub struct Vector { } impl Vector { + const SIMD_WIDTH: usize = 4; pub fn new(values: Vec) -> Self { let dimensions = values.len(); Self { dimensions, values } @@ -50,7 +52,11 @@ impl Vector { "Vectors must have the same dimensions" ); - self.dot_scalar(other) + if self.dimensions % Self::SIMD_WIDTH == 0 { + self.dot_simd(other) + } else { + self.dot_scalar(other) + } } fn dot_scalar(&self, other: &Vector) -> f32 { @@ -61,6 +67,26 @@ impl Vector { .sum() } + fn dot_simd(&self, other: &Vector) -> f32 { + let mut sum = f32x4::splat(0.0); + for i in (0..self.dimensions).step_by(Self::SIMD_WIDTH) { + let a = f32x4::new([ + self.values[i], + self.values[i + 1], + self.values[i + 2], + self.values[i + 3], + ]); + let b = f32x4::new([ + other.values[i], + other.values[i + 1], + other.values[i + 2], + other.values[i + 3], + ]); + sum = sum + a * b; + } + sum.reduce_add() + } + pub fn magnitude(&self) -> f32 { self.dot(self).sqrt() } @@ -95,7 +121,11 @@ impl Vector { "Vectors must have the same dimensions" ); - self.euclidean_distance_scalar(other) + if self.dimensions % Self::SIMD_WIDTH == 0 { + self.euclidean_distance_simd(other) + } else { + self.euclidean_distance_scalar(other) + } } fn euclidean_distance_scalar(&self, other: &Vector) -> f32 { @@ -109,17 +139,83 @@ impl Vector { sum_squared_diff.sqrt() } + fn euclidean_distance_simd(&self, other: &Vector) -> f32 { + let mut sum = f32x4::splat(0.0); + for i in (0..self.dimensions).step_by(Self::SIMD_WIDTH) { + let a = f32x4::new([ + self.values[i], + self.values[i + 1], + self.values[i + 2], + self.values[i + 3], + ]); + let b = f32x4::new([ + other.values[i], + other.values[i + 1], + other.values[i + 2], + other.values[i + 3], + ]); + let diff = a - b; + sum = sum + diff * diff; + } + sum.reduce_add().sqrt() + } + + fn manhattan_distance_simd(&self, other: &Vector) -> f32 { + let mut sum = f32x4::splat(0.0); + for i in (0..self.dimensions).step_by(Self::SIMD_WIDTH) { + let a = f32x4::new([ + self.values[i], + self.values[i + 1], + self.values[i + 2], + self.values[i + 3], + ]); + let b = f32x4::new([ + other.values[i], + other.values[i + 1], + other.values[i + 2], + other.values[i + 3], + ]); + sum = sum + (a - b).abs(); + } + sum.reduce_add() + } + + fn hamming_distance_simd(&self, other: &Vector) -> usize { + let mut count = 0usize; + for i in (0..self.dimensions).step_by(Self::SIMD_WIDTH) { + let a = f32x4::new([ + self.values[i], + self.values[i + 1], + self.values[i + 2], + self.values[i + 3], + ]); + let b = f32x4::new([ + other.values[i], + other.values[i + 1], + other.values[i + 2], + other.values[i + 3], + ]); + let mask = (a - b).abs().cmp_gt(f32x4::splat(f32::EPSILON)); + count += mask.move_mask().count_ones() as usize; + } + count + } + pub fn manhattan_distance(&self, other: &Vector) -> f32 { assert_eq!( self.dimensions, other.dimensions, "Vectors must have the same dimensions" ); - self.values - .iter() - .zip(other.values.iter()) - .map(|(a, b)| (a - b).abs()) - .sum() + if self.dimensions % Self::SIMD_WIDTH == 0 { + self.manhattan_distance_simd(other) + } else { + self.values + .iter() + .zip(other.values.iter()) + .map(|(a, b)| (a - b).abs()) + .sum() + } } pub fn hamming_distance(&self, other: &Vector) -> usize { @@ -128,11 +224,15 @@ impl Vector { "Vectors must have the same dimensions" ); - self.values - .iter() - .zip(other.values.iter()) - .filter(|(&a, &b)| (a - b).abs() > f32::EPSILON) - .count() + if self.dimensions % Self::SIMD_WIDTH == 0 { + self.hamming_distance_simd(other) + } else { + self.values + .iter() + .zip(other.values.iter()) + .filter(|(&a, &b)| (a - b).abs() > f32::EPSILON) + .count() + } } pub fn batch_process(&self, others: &[Vector], f: F) -> Vec