use wasm_bindgen::prelude::*;
#[cfg(feature = "wasm-threads")]
pub use wasm_bindgen_rayon::init_thread_pool;
#[wasm_bindgen(js_name = isThreadedAvailable)]
pub fn is_threaded_available() -> bool {
#[wasm_bindgen(
inline_js = "export function crossOriginIsolated() { return globalThis.crossOriginIsolated === true; }"
)]
extern "C" {
#[wasm_bindgen(js_name = crossOriginIsolated)]
fn cross_origin_isolated() -> bool;
}
cross_origin_isolated()
}
#[wasm_bindgen(js_name = optimalThreadCount)]
pub fn optimal_thread_count() -> usize {
#[wasm_bindgen(
inline_js = "export function hardwareConcurrency() { return navigator.hardwareConcurrency || 1; }"
)]
extern "C" {
#[wasm_bindgen(js_name = hardwareConcurrency)]
fn hardware_concurrency() -> usize;
}
let hw_threads = hardware_concurrency();
if hw_threads <= 1 {
return 1;
}
let available = hw_threads.saturating_sub(1);
available.clamp(1, 8)
}
#[wasm_bindgen]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThreadingMode {
Parallel,
Sequential,
}
impl ThreadingMode {
#[must_use]
pub fn detect() -> Self {
if is_threaded_available() {
Self::Parallel
} else {
Self::Sequential
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Parallel => "Parallel (Web Workers)",
Self::Sequential => "Sequential (Single-threaded)",
}
}
#[must_use]
pub const fn performance_ratio(self) -> f32 {
match self {
Self::Parallel => 1.0,
Self::Sequential => 4.0, }
}
}
#[wasm_bindgen(js_name = getThreadingMode)]
pub fn get_threading_mode() -> ThreadingMode {
ThreadingMode::detect()
}
#[wasm_bindgen(js_name = getThreadingModeName)]
pub fn get_threading_mode_name() -> String {
ThreadingMode::detect().name().to_string()
}
#[cfg(feature = "parallel")]
mod parallel {
use rayon::prelude::*;
pub fn parallel_map<T, U, F>(items: &[T], f: F) -> Vec<U>
where
T: Sync,
U: Send,
F: Fn(&T) -> U + Sync + Send,
{
items.par_iter().map(f).collect()
}
pub fn parallel_reduce<T, F, R>(items: &[T], identity: T, f: F, reduce: R) -> T
where
T: Send + Sync + Clone,
F: Fn(&T) -> T + Sync + Send,
R: Fn(T, T) -> T + Sync + Send,
{
items.par_iter().map(f).reduce(|| identity.clone(), reduce)
}
pub fn parallel_matmul(
lhs: &[f32],
rhs: &[f32],
rows: usize,
cols: usize,
inner: usize,
) -> Vec<f32> {
let mut result = vec![0.0f32; rows * cols];
result
.par_chunks_mut(cols)
.enumerate()
.for_each(|(i, row)| {
for j in 0..cols {
let mut sum = 0.0f32;
for l in 0..inner {
sum += lhs[i * inner + l] * rhs[l * cols + j];
}
row[j] = sum;
}
});
result
}
}
#[cfg(feature = "parallel")]
pub use parallel::*;
#[cfg(not(feature = "parallel"))]
mod sequential {
pub fn parallel_map<T, U, F>(items: &[T], f: F) -> Vec<U>
where
F: Fn(&T) -> U,
{
items.iter().map(f).collect()
}
pub fn parallel_reduce<T, F, R>(items: &[T], identity: T, f: F, reduce: R) -> T
where
T: Clone,
F: Fn(&T) -> T,
R: Fn(T, T) -> T,
{
items.iter().map(f).fold(identity, reduce)
}
pub fn parallel_matmul(
lhs: &[f32],
rhs: &[f32],
rows: usize,
cols: usize,
inner: usize,
) -> Vec<f32> {
let mut result = vec![0.0f32; rows * cols];
for i in 0..rows {
for j in 0..cols {
let mut sum = 0.0f32;
for l in 0..inner {
sum += lhs[i * inner + l] * rhs[l * cols + j];
}
result[i * cols + j] = sum;
}
}
result
}
}
#[cfg(not(feature = "parallel"))]
pub use sequential::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_threading_mode_name() {
assert!(ThreadingMode::Parallel.name().contains("Parallel"));
assert!(ThreadingMode::Sequential.name().contains("Sequential"));
}
#[test]
fn test_threading_mode_performance_ratio() {
assert!((ThreadingMode::Parallel.performance_ratio() - 1.0).abs() < f32::EPSILON);
assert!(ThreadingMode::Sequential.performance_ratio() > 1.0);
}
#[test]
fn test_parallel_map() {
let items = vec![1, 2, 3, 4, 5];
let result = parallel_map(&items, |x| x * 2);
assert_eq!(result, vec![2, 4, 6, 8, 10]);
}
#[test]
fn test_parallel_reduce() {
let items = vec![1, 2, 3, 4, 5];
let result = parallel_reduce(&items, 0, |x| *x, |a, b| a + b);
assert_eq!(result, 15);
}
#[test]
fn test_parallel_matmul_2x2() {
let a = vec![1.0, 0.0, 0.0, 1.0];
let b = vec![1.0, 2.0, 3.0, 4.0];
let c = parallel_matmul(&a, &b, 2, 2, 2);
assert_eq!(c, vec![1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn test_parallel_matmul_3x3() {
let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
let b = vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
let c = parallel_matmul(&a, &b, 3, 3, 3);
assert_eq!(c, a); }
#[test]
fn test_parallel_matmul_nonsquare() {
let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let b = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let c = parallel_matmul(&a, &b, 2, 2, 3);
let expected = vec![22.0, 28.0, 49.0, 64.0];
assert_eq!(c, expected);
}
}