Skip to main content

fdars_core/
parallel.rs

1//! Parallel iteration abstraction for WASM compatibility.
2//!
3//! This module provides conditional parallel/sequential iteration based on
4//! the `parallel` feature flag. On native targets with the `parallel` feature,
5//! uses rayon for multi-threaded execution. On WASM or without the feature,
6//! falls back to sequential iteration.
7//!
8//! # Usage
9//!
10//! Use the `iter_maybe_parallel!` macro to conditionally parallelize iteration:
11//!
12//! ```ignore
13//! use crate::parallel::iter_maybe_parallel;
14//!
15//! let results: Vec<_> = iter_maybe_parallel!((0..n))
16//!     .map(|i| expensive_computation(i))
17//!     .collect();
18//! ```
19
20/// Macro for conditionally parallel iteration over ranges.
21///
22/// When the `parallel` feature is enabled, uses `into_par_iter()`.
23/// Otherwise, uses `into_iter()` for sequential execution.
24///
25/// # Examples
26///
27/// ```ignore
28/// use crate::iter_maybe_parallel;
29///
30/// // Range iteration
31/// let results: Vec<_> = iter_maybe_parallel!((0..100))
32///     .map(|i| i * 2)
33///     .collect();
34///
35/// // Vec iteration (consuming)
36/// let vec = vec![1, 2, 3];
37/// let results: Vec<_> = iter_maybe_parallel!(vec)
38///     .map(|x| x * 2)
39///     .collect();
40/// ```
41#[macro_export]
42macro_rules! iter_maybe_parallel {
43    ($expr:expr) => {{
44        #[cfg(feature = "parallel")]
45        {
46            use rayon::iter::IntoParallelIterator;
47
48            IntoParallelIterator::into_par_iter($expr)
49        }
50        #[cfg(not(feature = "parallel"))]
51        {
52            IntoIterator::into_iter($expr)
53        }
54    }};
55}
56
57/// Macro for conditionally parallel reference iteration over slices.
58///
59/// When the `parallel` feature is enabled, uses `par_iter()`.
60/// Otherwise, uses `iter()` for sequential execution.
61#[macro_export]
62macro_rules! slice_maybe_parallel {
63    ($expr:expr) => {{
64        #[cfg(feature = "parallel")]
65        {
66            use rayon::prelude::*;
67            $expr.par_iter()
68        }
69        #[cfg(not(feature = "parallel"))]
70        {
71            $expr.iter()
72        }
73    }};
74}
75
76/// Macro for conditionally parallel mutable iteration over slices.
77///
78/// When the `parallel` feature is enabled, uses `par_iter_mut()`.
79/// Otherwise, uses `iter_mut()` for sequential execution.
80#[macro_export]
81macro_rules! slice_maybe_parallel_mut {
82    ($expr:expr) => {{
83        #[cfg(feature = "parallel")]
84        {
85            use rayon::prelude::*;
86            $expr.par_iter_mut()
87        }
88        #[cfg(not(feature = "parallel"))]
89        {
90            $expr.iter_mut()
91        }
92    }};
93}
94
95/// Macro for parallel/sequential chunks iteration on mutable slices.
96///
97/// # Example
98/// ```ignore
99/// use crate::maybe_par_chunks_mut;
100///
101/// maybe_par_chunks_mut!(data, chunk_size, |chunk| {
102///     // process chunk
103/// });
104/// ```
105#[macro_export]
106macro_rules! maybe_par_chunks_mut {
107    ($slice:expr, $chunk_size:expr, $closure:expr) => {{
108        #[cfg(feature = "parallel")]
109        {
110            use rayon::prelude::*;
111            $slice.par_chunks_mut($chunk_size).for_each($closure);
112        }
113        #[cfg(not(feature = "parallel"))]
114        {
115            $slice.chunks_mut($chunk_size).for_each($closure);
116        }
117    }};
118}
119
120/// Macro for enumerated parallel/sequential chunks iteration.
121///
122/// # Example
123/// ```ignore
124/// use crate::maybe_par_chunks_mut_enumerate;
125///
126/// maybe_par_chunks_mut_enumerate!(data, chunk_size, |(idx, chunk)| {
127///     // process chunk at index idx
128/// });
129/// ```
130#[macro_export]
131macro_rules! maybe_par_chunks_mut_enumerate {
132    ($slice:expr, $chunk_size:expr, $closure:expr) => {{
133        #[cfg(feature = "parallel")]
134        {
135            use rayon::prelude::*;
136            $slice
137                .par_chunks_mut($chunk_size)
138                .enumerate()
139                .for_each($closure);
140        }
141        #[cfg(not(feature = "parallel"))]
142        {
143            $slice
144                .chunks_mut($chunk_size)
145                .enumerate()
146                .for_each($closure);
147        }
148    }};
149}
150
151// Re-export macros at module level
152pub use iter_maybe_parallel;
153pub use maybe_par_chunks_mut;
154pub use maybe_par_chunks_mut_enumerate;
155pub use slice_maybe_parallel;
156pub use slice_maybe_parallel_mut;
157
158#[cfg(test)]
159mod tests {
160    #[cfg(feature = "parallel")]
161    use rayon::iter::ParallelIterator;
162
163    #[test]
164    fn test_iter_maybe_parallel_range() {
165        let mut result: Vec<usize> = iter_maybe_parallel!(0..10).map(|i| i * 2).collect();
166        result.sort(); // rayon may reorder
167        assert_eq!(result, vec![0, 2, 4, 6, 8, 10, 12, 14, 16, 18]);
168    }
169
170    #[test]
171    fn test_slice_maybe_parallel_map() {
172        let vec = vec![1.0_f64, 2.0, 3.0, 4.0, 5.0];
173        let mut result: Vec<f64> = slice_maybe_parallel!(vec).map(|&x| x * x).collect();
174        result.sort_by(|a, b| a.partial_cmp(b).unwrap());
175        assert_eq!(result, vec![1.0, 4.0, 9.0, 16.0, 25.0]);
176    }
177
178    #[test]
179    fn test_slice_maybe_parallel_mut() {
180        let mut vec = vec![1.0_f64, 2.0, 3.0, 4.0];
181        slice_maybe_parallel_mut!(vec).for_each(|x| *x *= 3.0);
182        assert_eq!(vec, vec![3.0, 6.0, 9.0, 12.0]);
183    }
184
185    #[test]
186    fn test_maybe_par_chunks_mut() {
187        let mut vec = vec![0.0_f64; 12];
188        maybe_par_chunks_mut!(vec, 4, |chunk: &mut [f64]| {
189            for x in chunk.iter_mut() {
190                *x = 1.0;
191            }
192        });
193        assert!(vec.iter().all(|&x| x == 1.0));
194    }
195
196    #[test]
197    fn test_maybe_par_chunks_mut_enumerate() {
198        let mut vec = vec![0.0_f64; 12];
199        maybe_par_chunks_mut_enumerate!(vec, 4, |(idx, chunk): (usize, &mut [f64])| {
200            for x in chunk.iter_mut() {
201                *x = idx as f64;
202            }
203        });
204        assert_eq!(
205            vec,
206            vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0]
207        );
208    }
209}