use std::mem;
use super::{CACHE_CHUNK_SIZE, CACHE_LINE_SIZE, prefetch_read_data};
use crate::{
base::SendPtr,
parallel::output::{ChunkWriter, MapOutput, output_chunk_range},
};
pub(super) const DEFAULT_RING_BUFFER_CAPACITY: usize = 1_024;
pub struct ZeroCopyParallelIter<'a, T> {
pub(super) data: &'a [T],
pub(super) chunk_size: usize,
}
impl<'a, T: Sync> ZeroCopyParallelIter<'a, T> {
pub fn new(data: &'a [T]) -> Self {
let chunk_size = zero_copy_chunk_size_for_lanes(
data.len(),
mem::size_of::<T>(),
crate::base::process_parallelism(),
);
Self { data, chunk_size }
}
pub fn for_each<F>(&self, func: F)
where
F: Fn(&T) + Send + Sync,
{
if !should_execute_scoped_cache::<T>(self.data.len(), self.chunk_size) {
self.data.iter().for_each(func);
return;
}
let chunks: Vec<_> = self.data.chunks(self.chunk_size).collect();
let num_chunks = chunks.len();
let func_ptr = SendPtr(&func as *const F as *const () as *mut ());
let visit_chunk = |index: usize| {
unsafe {
let chunk = *chunks.get_unchecked(index);
let chunk_slice = std::slice::from_raw_parts(chunk.as_ptr(), chunk.len());
let func_ref = &*(func_ptr.as_ptr() as *const F);
let cache_line_elements = (CACHE_LINE_SIZE / mem::size_of::<T>().max(1)).max(1);
for (offset, item) in chunk_slice.iter().enumerate() {
if offset % cache_line_elements == 0
&& offset + cache_line_elements < chunk_slice.len()
{
prefetch_read_data(
chunk_slice
.as_ptr()
.add(offset + cache_line_elements)
.cast(),
0,
);
}
func_ref(item);
}
}
};
let run_on_global = moirai_executor::global()
.for_each_indexed::<moirai_executor::schedule::SyncTask, _>(num_chunks, &visit_chunk);
if crate::base::sequential_fallback_permitted(&run_on_global) {
(0..num_chunks).for_each(visit_chunk);
}
}
pub fn map<F, R>(&self, func: F) -> Vec<R>
where
F: Fn(&T) -> R + Send + Sync,
R: Send,
{
if !should_execute_scoped_cache::<T>(self.data.len(), self.chunk_size) {
return self.data.iter().map(&func).collect();
}
let mut output = MapOutput::new(self.data.len(), self.chunk_size);
let num_chunks = output.chunk_count();
let data_ptr = SendPtr(self.data.as_ptr().cast_mut().cast::<()>());
let output_ptr = SendPtr(output.values_ptr().cast::<()>());
let completed_ptr = SendPtr(output.completed_ptr().cast::<()>());
let func_ptr = SendPtr(&func as *const F as *const () as *mut ());
let map_chunk = |chunk_index: usize| {
unsafe {
let chunk_range = output_chunk_range(self.data.len(), self.chunk_size, chunk_index);
let chunk = std::slice::from_raw_parts(
data_ptr.as_ptr().cast::<T>().add(chunk_range.start),
chunk_range.len(),
);
let func_ref = &*(func_ptr.as_ptr() as *const F);
let mut writer = ChunkWriter::new(output_ptr.as_ptr().cast(), chunk_range);
for item in chunk {
writer.push(func_ref(item));
}
completed_ptr
.as_ptr()
.cast::<usize>()
.add(chunk_index)
.write(writer.finish());
}
};
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);
}
output.into_vec()
}
pub fn reduce<F>(&self, func: F) -> Option<T>
where
F: Fn(&T, &T) -> T + Send + Sync,
T: Clone + Send,
{
if self.data.is_empty() {
return None;
}
if self.data.len() == 1 {
return Some(self.data[0].clone());
}
if !should_execute_scoped_cache::<T>(self.data.len(), self.chunk_size) {
return self
.data
.iter()
.cloned()
.reduce(|left, right| func(&left, &right));
}
let chunks: Vec<_> = self.data.chunks(self.chunk_size).collect();
let num_chunks = chunks.len();
let mut results = (0..num_chunks).map(|_| None).collect::<Vec<_>>();
let results_ptr = SendPtr(results.as_mut_ptr().cast::<()>());
let func_ptr = SendPtr(&func as *const F as *const () as *mut ());
let reduce_chunk = |index: usize| {
unsafe {
let chunk = *chunks.get_unchecked(index);
let chunk_slice = std::slice::from_raw_parts(chunk.as_ptr(), chunk.len());
let func_ref = &*(func_ptr.as_ptr() as *const F);
let chunk_result = chunk_slice
.iter()
.cloned()
.reduce(|left, right| func_ref(&left, &right));
results_ptr
.as_ptr()
.cast::<Option<T>>()
.add(index)
.write(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);
}
let mut current_results: Vec<T> = results.into_iter().flatten().collect();
while current_results.len() > 1 {
current_results = reduce_owned_pairs(current_results, &func);
}
current_results.into_iter().next()
}
}
pub(super) fn reduce_owned_pairs<T, F>(items: Vec<T>, func: &F) -> Vec<T>
where
F: Fn(&T, &T) -> T,
{
let mut source = items.into_iter();
let mut reduced = Vec::with_capacity(source.len().div_ceil(2));
while let Some(left) = source.next() {
match source.next() {
Some(right) => reduced.push(func(&left, &right)),
None => reduced.push(left),
}
}
reduced
}
#[inline]
pub(super) fn should_execute_scoped_cache<T>(len: usize, chunk_size: usize) -> bool {
let cache_chunk_items = (CACHE_CHUNK_SIZE / mem::size_of::<T>().max(1)).max(1);
let scoped_item_floor = cache_chunk_items.saturating_mul(DEFAULT_RING_BUFFER_CAPACITY);
len > chunk_size && len > scoped_item_floor
}
pub(super) const fn zero_copy_chunk_size_for_lanes(
len: usize,
element_size: usize,
lane_count: usize,
) -> usize {
let lanes = if lane_count == 0 { 1 } else { lane_count };
let width = if element_size == 0 { 1 } else { element_size };
let elements_per_cache_chunk = CACHE_CHUNK_SIZE / width;
let fair_share = len / lanes;
let chunk_size = if fair_share > elements_per_cache_chunk {
fair_share
} else {
elements_per_cache_chunk
};
if chunk_size == 0 { 1 } else { chunk_size }
}