#![cfg(feature = "par")]
use alloc::vec::Vec;
use super::Nodus;
#[cfg(feature = "gpu-wgpu")]
pub mod wgpu;
pub trait Backend {
fn collect<T>(&self, node: &dyn Nodus<Item = T>) -> Vec<T>
where
T: Clone + Send + Sync + 'static;
fn reduce<T, F>(&self, node: &dyn Nodus<Item = T>, f: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(T, T) -> T + Send + Sync;
fn reduce_gpu<T, F>(&self, node: &dyn Nodus<Item = T>, wgsl_op: &str, fallback: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(T, T) -> T + Send + Sync,
{
let _ = wgsl_op; self.reduce(node, fallback)
}
fn fold<T, B, F>(&self, node: &dyn Nodus<Item = T>, init: B, f: F) -> B
where
T: Clone + Send + Sync + 'static,
B: Clone + Send + Sync + 'static,
F: Fn(B, T) -> B + Send + Sync;
fn for_each<T, F>(&self, node: &dyn Nodus<Item = T>, f: F)
where
T: Clone + Send + Sync + 'static,
F: Fn(T) + Send + Sync;
fn any<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> bool
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync;
fn all<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> bool
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync;
fn find<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync;
fn count<T>(&self, node: &dyn Nodus<Item = T>) -> usize
where
T: Clone + Send + Sync + 'static;
}
#[inline]
fn reduce_scalar<T>(node: &dyn Nodus<Item = T>, f: &dyn Fn(T, T) -> T) -> Option<T>
where
T: Clone + Send + Sync + 'static,
{
let mut acc: Option<T> = None;
node.visit_scalar(&mut |x| {
acc = Some(match acc.take() {
None => x,
Some(a) => f(a, x),
});
});
acc
}
#[inline]
fn fold_scalar<T, B>(node: &dyn Nodus<Item = T>, init: B, f: &dyn Fn(B, T) -> B) -> B
where
T: Clone + Send + Sync + 'static,
B: Clone,
{
let mut acc: Option<B> = Some(init);
node.visit_scalar(&mut |x| {
let old = acc.take().unwrap();
acc = Some(f(old, x));
});
acc.unwrap()
}
#[inline]
fn for_each_scalar<T>(node: &dyn Nodus<Item = T>, f: &dyn Fn(T))
where
T: Clone + Send + Sync + 'static,
{
node.visit_scalar(&mut |x| f(x));
}
#[inline]
fn any_scalar<T>(node: &dyn Nodus<Item = T>, predicate: &dyn Fn(&T) -> bool) -> bool
where
T: Clone + Send + Sync + 'static,
{
use core::ops::ControlFlow;
let mut found = false;
node.try_visit_scalar_ref(&mut |x| {
if predicate(x) {
found = true;
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
});
found
}
#[inline]
fn all_scalar<T>(node: &dyn Nodus<Item = T>, predicate: &dyn Fn(&T) -> bool) -> bool
where
T: Clone + Send + Sync + 'static,
{
use core::ops::ControlFlow;
let mut all_match = true;
node.try_visit_scalar_ref(&mut |x| {
if predicate(x) {
ControlFlow::Continue(())
} else {
all_match = false;
ControlFlow::Break(())
}
});
all_match
}
#[inline]
fn find_scalar<T>(node: &dyn Nodus<Item = T>, predicate: &dyn Fn(&T) -> bool) -> Option<T>
where
T: Clone + Send + Sync + 'static,
{
use core::ops::ControlFlow;
let mut result: Option<T> = None;
node.try_visit_scalar_ref(&mut |x| {
if predicate(x) {
if result.is_none() {
result = Some(x.clone());
}
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
});
result
}
#[inline]
fn count_scalar<T>(node: &dyn Nodus<Item = T>) -> usize
where
T: Clone + Send + Sync + 'static,
{
let mut count = 0;
node.visit_scalar_ref(&mut |_| count += 1);
count
}
#[derive(Clone, Copy, Debug, Default)]
pub struct CpuScalar;
impl Backend for CpuScalar {
#[inline]
fn collect<T>(&self, node: &dyn Nodus<Item = T>) -> Vec<T>
where
T: Clone + Send + Sync + 'static,
{
node.collect_scalar()
}
#[inline]
fn reduce<T, F>(&self, node: &dyn Nodus<Item = T>, f: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(T, T) -> T + Send + Sync,
{
reduce_scalar(node, &f)
}
#[inline]
fn fold<T, B, F>(&self, node: &dyn Nodus<Item = T>, init: B, f: F) -> B
where
T: Clone + Send + Sync + 'static,
B: Clone + Send + Sync + 'static,
F: Fn(B, T) -> B + Send + Sync,
{
fold_scalar(node, init, &f)
}
#[inline]
fn for_each<T, F>(&self, node: &dyn Nodus<Item = T>, f: F)
where
T: Clone + Send + Sync + 'static,
F: Fn(T) + Send + Sync,
{
for_each_scalar(node, &f);
}
#[inline]
fn any<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> bool
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
any_scalar(node, &predicate)
}
#[inline]
fn all<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> bool
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
all_scalar(node, &predicate)
}
#[inline]
fn find<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
find_scalar(node, &predicate)
}
#[inline]
fn count<T>(&self, node: &dyn Nodus<Item = T>) -> usize
where
T: Clone + Send + Sync + 'static,
{
if node.is_indexed() {
node.len()
} else {
count_scalar(node)
}
}
}
#[cfg(feature = "rayon")]
const PARALLEL_OVERHEAD_NS: u64 = 100_000;
#[cfg(feature = "rayon")]
const DEFAULT_COST_NS_PER_ELEM: u32 = 2;
#[cfg(feature = "rayon")]
#[derive(Clone, Copy, Debug)]
pub struct CpuRayon {
pub min_len: usize,
}
#[cfg(feature = "rayon")]
impl CpuRayon {
const MIN_PARALLEL_FLOOR: usize = 16;
#[must_use]
pub fn for_cost_ns(cost_ns_per_elem: u32) -> Self {
let min_len = if cost_ns_per_elem == 0 {
usize::MAX
} else {
let break_even = PARALLEL_OVERHEAD_NS / u64::from(cost_ns_per_elem);
(break_even as usize).max(Self::MIN_PARALLEL_FLOOR)
};
Self { min_len }
}
}
#[cfg(feature = "rayon")]
impl Default for CpuRayon {
fn default() -> Self {
Self::for_cost_ns(DEFAULT_COST_NS_PER_ELEM)
}
}
#[cfg(feature = "rayon")]
impl Backend for CpuRayon {
#[inline]
fn collect<T>(&self, node: &dyn Nodus<Item = T>) -> Vec<T>
where
T: Clone + Send + Sync + 'static,
{
if node.len() < self.min_len {
return node.collect_scalar();
}
node.collect_rayon()
}
#[inline]
fn reduce<T, F>(&self, node: &dyn Nodus<Item = T>, f: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(T, T) -> T + Send + Sync,
{
if node.len() < self.min_len {
return reduce_scalar(node, &f);
}
node.reduce_rayon(&f)
}
#[inline]
fn fold<T, B, F>(&self, node: &dyn Nodus<Item = T>, init: B, f: F) -> B
where
T: Clone + Send + Sync + 'static,
B: Clone + Send + Sync + 'static,
F: Fn(B, T) -> B + Send + Sync,
{
fold_scalar(node, init, &f)
}
#[inline]
fn for_each<T, F>(&self, node: &dyn Nodus<Item = T>, f: F)
where
T: Clone + Send + Sync + 'static,
F: Fn(T) + Send + Sync,
{
if node.len() < self.min_len {
return for_each_scalar(node, &f);
}
node.for_each_rayon(&f);
}
#[inline]
fn any<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> bool
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
use rayon::prelude::*;
if node.len() < self.min_len || !node.is_indexed() {
return any_scalar(node, &predicate);
}
let pred_ref = &predicate;
(0..node.len())
.into_par_iter()
.any(|i| pred_ref(&node.get(i)))
}
#[inline]
fn all<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> bool
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
use rayon::prelude::*;
if node.len() < self.min_len || !node.is_indexed() {
return all_scalar(node, &predicate);
}
let pred_ref = &predicate;
(0..node.len())
.into_par_iter()
.all(|i| pred_ref(&node.get(i)))
}
#[inline]
fn find<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
use rayon::prelude::*;
if node.len() < self.min_len || !node.is_indexed() {
return find_scalar(node, &predicate);
}
let pred_ref = &predicate;
(0..node.len()).into_par_iter().find_map_first(|i| {
let item = node.get(i);
if pred_ref(&item) { Some(item) } else { None }
})
}
#[inline]
fn count<T>(&self, node: &dyn Nodus<Item = T>) -> usize
where
T: Clone + Send + Sync + 'static,
{
if node.is_indexed() {
return node.len();
}
count_scalar(node)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct CpuSimd;
impl CpuSimd {
#[inline]
pub fn sum_f32(&self, data: &[f32]) -> f32 {
super::simd::simd_sum_f32(data)
}
#[inline]
pub fn dot_f32(&self, a: &[f32], b: &[f32]) -> f32 {
super::simd::simd_dot_f32(a, b)
}
#[inline]
pub fn add_f32(&self, a: &[f32], b: &[f32]) -> Vec<f32> {
super::simd::simd_add_f32(a, b)
}
#[inline]
pub fn mul_f32(&self, a: &[f32], b: &[f32]) -> Vec<f32> {
super::simd::simd_mul_f32(a, b)
}
#[inline]
pub fn scale_f32(&self, data: &[f32], scale: f32) -> Vec<f32> {
super::simd::simd_scale_f32(data, scale)
}
#[inline]
pub fn map_f32<F: Fn(f32) -> f32>(&self, data: &[f32], f: F) -> Vec<f32> {
super::simd::simd_map_f32(data, f)
}
#[inline]
pub fn min_f32(&self, data: &[f32]) -> Option<f32> {
super::simd::simd_min_f32(data)
}
#[inline]
pub fn max_f32(&self, data: &[f32]) -> Option<f32> {
super::simd::simd_max_f32(data)
}
#[inline]
pub fn sum_stream_f32(&self, node: &dyn super::Nodus<Item = f32>) -> f32 {
let data = node.collect_scalar();
super::simd::simd_sum_f32(&data)
}
#[inline]
pub fn min_stream_f32(&self, node: &dyn super::Nodus<Item = f32>) -> Option<f32> {
let data = node.collect_scalar();
super::simd::simd_min_f32(&data)
}
#[inline]
pub fn max_stream_f32(&self, node: &dyn super::Nodus<Item = f32>) -> Option<f32> {
let data = node.collect_scalar();
super::simd::simd_max_f32(&data)
}
#[inline]
pub fn dot_stream_f32(
&self,
a: &dyn super::Nodus<Item = f32>,
b: &dyn super::Nodus<Item = f32>,
) -> f32 {
let va = a.collect_scalar();
let vb = b.collect_scalar();
super::simd::simd_dot_f32(&va, &vb)
}
#[inline]
pub fn fold_f32<F>(&self, node: &dyn super::Nodus<Item = f32>, init: f32, f: F) -> f32
where
F: Fn(f32, f32) -> f32 + Send + Sync,
{
let data = node.collect_scalar();
data.into_iter().fold(init, f)
}
#[inline]
pub fn scale_stream_f32(
&self,
node: &dyn super::Nodus<Item = f32>,
scale: f32,
) -> alloc::vec::Vec<f32> {
let data = node.collect_scalar();
super::simd::simd_scale_f32(&data, scale)
}
#[inline]
pub fn add_streams_f32(
&self,
a: &dyn super::Nodus<Item = f32>,
b: &dyn super::Nodus<Item = f32>,
) -> alloc::vec::Vec<f32> {
let va = a.collect_scalar();
let vb = b.collect_scalar();
super::simd::simd_add_f32(&va, &vb)
}
}
impl Backend for CpuSimd {
#[inline]
fn collect<T>(&self, node: &dyn Nodus<Item = T>) -> Vec<T>
where
T: Clone + Send + Sync + 'static,
{
node.collect_scalar()
}
#[inline]
fn reduce<T, F>(&self, node: &dyn Nodus<Item = T>, f: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(T, T) -> T + Send + Sync,
{
reduce_scalar(node, &f)
}
#[inline]
fn fold<T, B, F>(&self, node: &dyn Nodus<Item = T>, init: B, f: F) -> B
where
T: Clone + Send + Sync + 'static,
B: Clone + Send + Sync + 'static,
F: Fn(B, T) -> B + Send + Sync,
{
fold_scalar(node, init, &f)
}
#[inline]
fn for_each<T, F>(&self, node: &dyn Nodus<Item = T>, f: F)
where
T: Clone + Send + Sync + 'static,
F: Fn(T) + Send + Sync,
{
for_each_scalar(node, &f);
}
#[inline]
fn any<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> bool
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
any_scalar(node, &predicate)
}
#[inline]
fn all<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> bool
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
all_scalar(node, &predicate)
}
#[inline]
fn find<T, F>(&self, node: &dyn Nodus<Item = T>, predicate: F) -> Option<T>
where
T: Clone + Send + Sync + 'static,
F: Fn(&T) -> bool + Send + Sync,
{
find_scalar(node, &predicate)
}
#[inline]
fn count<T>(&self, node: &dyn Nodus<Item = T>) -> usize
where
T: Clone + Send + Sync + 'static,
{
if node.is_indexed() {
node.len()
} else {
count_scalar(node)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
struct SimpleNode {
data: Vec<i32>,
}
impl Nodus for SimpleNode {
type Item = i32;
fn len(&self) -> usize {
self.data.len()
}
fn visit_scalar(&self, sink: &mut dyn FnMut(Self::Item)) {
for &x in &self.data {
sink(x);
}
}
fn is_indexed(&self) -> bool {
true
}
fn get(&self, index: usize) -> Self::Item {
self.data[index]
}
#[cfg(feature = "rayon")]
fn collect_rayon(&self) -> Vec<Self::Item> {
use rayon::prelude::*;
self.data.par_iter().cloned().collect()
}
}
#[test]
fn test_cpu_scalar_collect() {
let node = SimpleNode {
data: vec![1, 2, 3],
};
let result = CpuScalar.collect(&node);
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn test_cpu_scalar_reduce() {
let node = SimpleNode {
data: vec![1, 2, 3, 4],
};
let result = CpuScalar.reduce(&node, |a, b| a + b);
assert_eq!(result, Some(10));
}
#[test]
fn test_cpu_scalar_fold() {
let node = SimpleNode {
data: vec![1, 2, 3],
};
let result = CpuScalar.fold(&node, 0, |acc, x| acc + x);
assert_eq!(result, 6);
}
#[test]
fn test_cpu_scalar_any() {
let node = SimpleNode {
data: vec![1, 2, 3, 4, 5],
};
assert!(CpuScalar.any(&node, |x| *x > 3));
assert!(!CpuScalar.any(&node, |x| *x > 10));
}
#[test]
fn test_cpu_scalar_all() {
let node = SimpleNode {
data: vec![2, 4, 6],
};
assert!(CpuScalar.all(&node, |x| x % 2 == 0));
assert!(!CpuScalar.all(&node, |x| *x > 3));
}
#[test]
fn test_cpu_scalar_find() {
let node = SimpleNode {
data: vec![1, 2, 3, 4, 5],
};
assert_eq!(CpuScalar.find(&node, |x| *x > 3), Some(4));
assert_eq!(CpuScalar.find(&node, |x| *x > 10), None);
}
#[test]
fn test_cpu_scalar_count() {
let node = SimpleNode {
data: vec![1, 2, 3, 4, 5],
};
assert_eq!(CpuScalar.count(&node), 5);
}
#[cfg(feature = "rayon")]
mod rayon_tests {
use super::*;
#[test]
fn test_cpu_rayon_collect_small() {
let node = SimpleNode {
data: vec![1, 2, 3],
};
let backend = CpuRayon { min_len: 10 };
let result = backend.collect(&node);
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn test_cpu_rayon_work_aware_threshold() {
assert_eq!(CpuRayon::for_cost_ns(2).min_len, 50_000);
assert_eq!(CpuRayon::for_cost_ns(100).min_len, 1_000);
assert_eq!(CpuRayon::for_cost_ns(500).min_len, 200);
assert_eq!(CpuRayon::for_cost_ns(u32::MAX).min_len, 16);
assert_eq!(CpuRayon::for_cost_ns(0).min_len, usize::MAX);
assert_eq!(CpuRayon::default().min_len, 50_000);
}
#[test]
fn test_cpu_rayon_default_stays_scalar_for_cheap_small_input() {
let backend = CpuRayon::default();
assert!(1000 < backend.min_len);
let node = SimpleNode {
data: (0..1000i32).collect(),
};
assert_eq!(
backend.reduce(&node, |a, b| a + b),
Some((0..1000i32).sum())
);
}
#[test]
fn test_cpu_rayon_reduce() {
let data: Vec<i32> = (1..=100).collect();
let node = SimpleNode { data };
let backend = CpuRayon { min_len: 10 };
let result = backend.reduce(&node, |a, b| a + b);
assert_eq!(result, Some(5050));
}
#[test]
fn test_cpu_rayon_any() {
let data: Vec<i32> = (1..=100).collect();
let node = SimpleNode { data };
let backend = CpuRayon { min_len: 10 };
assert!(backend.any(&node, |x| *x > 50));
assert!(!backend.any(&node, |x| *x > 200));
}
#[test]
fn test_cpu_rayon_all() {
let data: Vec<i32> = (1..=100).collect();
let node = SimpleNode { data };
let backend = CpuRayon { min_len: 10 };
assert!(backend.all(&node, |x| *x <= 100));
assert!(!backend.all(&node, |x| *x > 50));
}
}
mod simd_backend_tests {
use super::*;
#[test]
fn test_cpu_simd_sum() {
let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let backend = CpuSimd;
assert_eq!(backend.sum_f32(&data), 36.0);
}
#[test]
fn test_cpu_simd_dot() {
let a = vec![1.0f32, 2.0, 3.0, 4.0];
let b = vec![5.0f32, 6.0, 7.0, 8.0];
let backend = CpuSimd;
assert_eq!(backend.dot_f32(&a, &b), 70.0);
}
#[test]
fn test_cpu_simd_scale() {
let data = vec![1.0f32, 2.0, 3.0, 4.0];
let backend = CpuSimd;
assert_eq!(backend.scale_f32(&data, 2.0), vec![2.0, 4.0, 6.0, 8.0]);
}
#[test]
fn test_cpu_simd_map() {
let data = vec![1.0f32, 4.0, 9.0, 16.0];
let backend = CpuSimd;
let result = backend.map_f32(&data, f32::sqrt);
assert_eq!(result, vec![1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn test_cpu_simd_min_max() {
let data = vec![3.0f32, 1.0, 4.0, 1.0, 5.0, 9.0, 2.0, 6.0];
let backend = CpuSimd;
assert_eq!(backend.min_f32(&data), Some(1.0));
assert_eq!(backend.max_f32(&data), Some(9.0));
}
#[test]
fn test_cpu_simd_add_mul() {
let a = vec![1.0f32, 2.0, 3.0, 4.0];
let b = vec![5.0f32, 6.0, 7.0, 8.0];
let backend = CpuSimd;
assert_eq!(backend.add_f32(&a, &b), vec![6.0, 8.0, 10.0, 12.0]);
assert_eq!(backend.mul_f32(&a, &b), vec![5.0, 12.0, 21.0, 32.0]);
}
}
}