use crate::base::SendPtr;
const DEFAULT_RING_BUFFER_CAPACITY: usize = 1024;
pub struct ParallelIter<T> {
data: Vec<T>,
chunk_size: usize,
}
impl<T: Send + Sync> ParallelIter<T> {
#[inline]
pub fn new(data: Vec<T>) -> Self {
Self {
chunk_size: chunk_size(data.len()),
data,
}
}
#[inline]
pub fn map<F, U>(self, f: F) -> Vec<U>
where
F: Fn(&T) -> U + Send + Sync,
U: Send,
{
let data = self.data;
let chunk_size = self.chunk_size;
if !should_execute_scoped(data.len(), chunk_size) {
return data.iter().map(&f).collect();
}
let chunks: Vec<_> = data.chunks(chunk_size).collect();
let num_chunks = chunks.len();
let mut results: Vec<Option<Vec<U>>> = Vec::with_capacity(num_chunks);
for _ in 0..num_chunks {
results.push(None);
}
let results_ptr = SendPtr(results.as_mut_ptr() as *mut ());
let f_ptr_send = SendPtr(&f as *const F as *const () as *mut ());
let map_chunk = |idx: usize| unsafe {
let chunk = *chunks.get_unchecked(idx);
let chunk_ptr = chunk.as_ptr();
let chunk_len = chunk.len();
let chunk_slice = std::slice::from_raw_parts(chunk_ptr, chunk_len);
let f_ref = &*(f_ptr_send.as_ptr() as *const F);
let chunk_result = chunk_slice.iter().map(f_ref).collect::<Vec<_>>();
*(results_ptr.as_ptr() as *mut Option<Vec<U>>).add(idx) = Some(chunk_result);
};
let run_on_global = moirai_executor::global()
.for_each_indexed::<moirai_executor::schedule::SyncTask, _>(num_chunks, &map_chunk);
if crate::base::sequential_fallback_permitted(&run_on_global) {
(0..num_chunks).for_each(map_chunk);
}
results.into_iter().flatten().flatten().collect()
}
#[inline]
pub fn reduce<F>(self, identity: T, f: F) -> T
where
F: Fn(T, &T) -> T + Send + Sync,
T: Clone,
{
let data = self.data;
let chunk_size = self.chunk_size;
if !should_execute_scoped(data.len(), chunk_size) {
return data.iter().fold(identity, &f);
}
let chunks: Vec<_> = data.chunks(chunk_size).collect();
let num_chunks = chunks.len();
let mut results: Vec<Option<T>> = Vec::with_capacity(num_chunks);
for _ in 0..num_chunks {
results.push(None);
}
let mut chunk_identities = vec![identity.clone(); num_chunks];
let identities_ptr = SendPtr(chunk_identities.as_mut_ptr() as *mut ());
let results_ptr = SendPtr(results.as_mut_ptr() as *mut ());
let f_ptr_send = SendPtr(&f as *const F as *const () as *mut ());
let reduce_chunk = |idx: usize| unsafe {
let chunk = *chunks.get_unchecked(idx);
let chunk_ptr = chunk.as_ptr();
let chunk_len = chunk.len();
let chunk_slice = std::slice::from_raw_parts(chunk_ptr, chunk_len);
let f_ref = &*(f_ptr_send.as_ptr() as *const F);
let chunk_identity = (*(identities_ptr.as_ptr() as *const T).add(idx)).clone();
let chunk_result = chunk_slice.iter().fold(chunk_identity, f_ref);
*(results_ptr.as_ptr() as *mut Option<T>).add(idx) = Some(chunk_result);
};
let run_on_global = moirai_executor::global()
.for_each_indexed::<moirai_executor::schedule::SyncTask, _>(num_chunks, &reduce_chunk);
if crate::base::sequential_fallback_permitted(&run_on_global) {
(0..num_chunks).for_each(reduce_chunk);
}
results
.into_iter()
.flatten()
.fold(identity, |accumulator, value| f(accumulator, &value))
}
}
#[inline]
fn chunk_size(len: usize) -> usize {
let worker_count = themis::CpuTopology::detect()
.map(|topology| topology.logical_processors())
.or_else(|| std::thread::available_parallelism().ok().map(|n| n.get()))
.unwrap_or(1)
.max(1);
len.div_ceil(worker_count).max(1)
}
#[inline]
fn should_execute_scoped(len: usize, chunk_size: usize) -> bool {
len > chunk_size && chunk_size > DEFAULT_RING_BUFFER_CAPACITY
}
#[cfg(test)]
mod tests {
use super::*;
fn chunked<T: Send + Sync>(data: Vec<T>, chunk_size: usize) -> ParallelIter<T> {
assert!(
should_execute_scoped(data.len(), chunk_size),
"the fixture must reach the parallel path, not the sequential fallback"
);
ParallelIter { data, chunk_size }
}
const CHUNK: usize = DEFAULT_RING_BUFFER_CAPACITY + 1;
const LEN: usize = CHUNK * 4 + 7;
#[test]
fn parallel_map_matches_the_sequential_result() {
let data: Vec<u64> = (0..LEN as u64).collect();
let expected: Vec<u64> = data.iter().map(|value| value * 3).collect();
let mapped = chunked(data, CHUNK).map(|value| value * 3);
assert_eq!(
mapped, expected,
"the chunked map must preserve every element and its order"
);
}
#[test]
fn parallel_reduce_matches_the_sequential_fold() {
let data: Vec<u64> = (0..LEN as u64).collect();
let expected: u64 = data.iter().sum();
let reduced = chunked(data, CHUNK).reduce(0, |sum, value| sum + value);
assert_eq!(
reduced, expected,
"every chunk's partial fold must reach the final result"
);
}
#[test]
fn parallel_map_covers_a_ragged_final_chunk() {
let data: Vec<u64> = (0..LEN as u64).collect();
let mapped = chunked(data, CHUNK).map(|value| *value);
assert_eq!(mapped.len(), LEN);
assert_eq!(mapped.last(), Some(&(LEN as u64 - 1)));
}
}