From 205c973ff670b15f2642b255aba8e3de1a7e40f0 Mon Sep 17 00:00:00 2001 From: teridax Date: Fri, 28 Apr 2023 18:43:29 +0200 Subject: [PATCH] added async dot product for sparse vector --- .idea/Rust-Programming.iml | 2 + sparse_vector/Cargo.toml | 10 ++++ sparse_vector/src/main.rs | 116 +++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 sparse_vector/Cargo.toml create mode 100644 sparse_vector/src/main.rs diff --git a/.idea/Rust-Programming.iml b/.idea/Rust-Programming.iml index a737a93..4779fc9 100644 --- a/.idea/Rust-Programming.iml +++ b/.idea/Rust-Programming.iml @@ -5,8 +5,10 @@ + + diff --git a/sparse_vector/Cargo.toml b/sparse_vector/Cargo.toml new file mode 100644 index 0000000..4803dee --- /dev/null +++ b/sparse_vector/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sparse_vector" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" +futures = "0.3.28" \ No newline at end of file diff --git a/sparse_vector/src/main.rs b/sparse_vector/src/main.rs new file mode 100644 index 0000000..cc51f0c --- /dev/null +++ b/sparse_vector/src/main.rs @@ -0,0 +1,116 @@ +use std::ops::{Add, Mul}; +use std::thread; +use futures::executor::block_on; +use rand::Rng; +use futures::future::{join_all}; + +/// Only stores more efficiently when at least 50% of all elements are zeros +pub struct SparseVec { + column: Vec<(usize, f32)> +} + +impl SparseVec { + + pub fn dot(&self, other: &SparseVec) -> f32 { + + let future = async move { + let divisions = 128; + + let k = self.column.len() / divisions; + + let mut futures = Vec::new(); + + for i in 0..divisions { + let off = i * k; + futures.push(dot_threaded(&self.column[off..(off + k)], &other.column[..])); + } + + join_all(futures).await + }; + + let result = block_on(future); + + block_on(async move { + let divisions = 16; + + let k = result.len() / divisions; + + let mut futures = Vec::new(); + + for i in 0..divisions { + let off = i * k; + futures.push(sum_async(&result[off..(off + k)])); + } + + join_all(futures).await + }).iter().fold(0.0, |acc, x| acc + x) + } + + pub fn new(elements: usize, null_prop: f32) -> Self { + let non_zero_elements = (elements as f32 * (1.0 - null_prop)) as usize; + + let mut column = Vec::with_capacity(non_zero_elements); + + let mut rng = rand::thread_rng(); + let mut last_idx = 0; + + for _ in 0..non_zero_elements { + last_idx = rng.gen_range(last_idx..elements); + column.push((last_idx, rng.gen_range(0.001..1.0))) + } + + Self { + column + } + } +} + +async fn sum_async(arr: &[f32]) -> f32 { + arr.iter().fold(0.0, |acc, x| acc + x) +} + +async fn dot_threaded(a: &[(usize, f32)], b: &[(usize, f32)]) -> f32 { + let mut sum = 0.0; + + for pair in a.iter() { + + // exponential search for an element in the second vector to have the same index + let mut bound = 1; + loop { + if bound >= b.len() || b[bound].1 >= pair.1 { + break; + } + + bound *= 2; + } + + let mut range = 0..bound; + loop { + let mut median = (range.end - range.start) / 2; + if median == 0 { + break; + } + median += range.start; + + if b[median].0 == pair.0 { + sum += b[median].1 * pair.1; + break; + } + + if b[median].0 > pair.0 { + range.end = median; + } else { + range.start = median; + } + } + } + + sum +} + +fn main() { + // generate a sparse vector with 10^10 random elements + let vec = SparseVec::new(10_000_000_000, 0.99); + + println!("{}", vec.dot(&vec)); +} \ No newline at end of file