use smallvec::SmallVec;
use crate::kernel::for_each_inner_block_preordered;
use crate::Result;
type SVec<T> = SmallVec<[T; 8]>;
pub(crate) struct SendPtr<T>(pub(crate) *mut T);
impl<T> Clone for SendPtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for SendPtr<T> {}
unsafe impl<T> Send for SendPtr<T> {}
unsafe impl<T> Sync for SendPtr<T> {}
impl<T> SendPtr<T> {
pub(crate) fn as_ptr(self) -> *mut T {
self.0
}
pub(crate) fn as_const(self) -> *const T {
self.0 as *const T
}
}
pub(crate) const MINTHREADLENGTH: usize = 1 << 15;
pub(crate) fn mapreduce_threaded<F>(
dims: &[usize],
blocks: &[usize],
strides_list: &[Vec<isize>],
offsets: &[isize],
costs: &[isize],
nthreads: usize,
spacing: isize,
taskindex: usize,
f: &F,
) -> Result<()>
where
F: Fn(&[usize], &[usize], &[Vec<isize>], &[isize]) -> Result<()> + Sync,
{
let total: usize = dims.iter().product();
if nthreads <= 1 || total <= MINTHREADLENGTH {
if spacing != 0 {
let mut spaced: SVec<isize> = SmallVec::from_slice(offsets);
spaced[0] += spacing * (taskindex as isize - 1);
return f(dims, blocks, strides_list, &spaced);
}
return f(dims, blocks, strides_list, offsets);
}
let (i, _) = dims.iter().zip(costs.iter()).enumerate().fold(
(0, isize::MIN),
|(best_i, best_v), (idx, (&d, &c))| {
let score = (d as isize - 1) * c;
if score >= best_v {
(idx, score)
} else {
(best_i, best_v)
}
},
);
if costs[i] == 0 || dims[i] <= blocks[i].min(1024) {
if spacing != 0 {
let mut spaced: SVec<isize> = SmallVec::from_slice(offsets);
spaced[0] += spacing * (taskindex as isize - 1);
return f(dims, blocks, strides_list, &spaced);
}
return f(dims, blocks, strides_list, offsets);
}
let di = dims[i];
let ndi = di / 2;
let nt_left = nthreads / 2;
let nt_right = nthreads - nt_left;
let mut left_dims: SVec<usize> = SmallVec::from_slice(dims);
left_dims[i] = ndi;
let mut right_dims: SVec<usize> = SmallVec::from_slice(dims);
right_dims[i] = di - ndi;
let mut right_offsets: SVec<isize> = SmallVec::from_slice(offsets);
for (k, strides) in strides_list.iter().enumerate() {
right_offsets[k] += ndi as isize * strides[i];
}
let left_offsets: SVec<isize> = SmallVec::from_slice(offsets);
let (r1, r2) = rayon::join(
|| {
mapreduce_threaded(
&left_dims,
blocks,
strides_list,
&left_offsets,
costs,
nt_left,
spacing,
taskindex,
f,
)
},
|| {
mapreduce_threaded(
&right_dims,
blocks,
strides_list,
&right_offsets,
costs,
nt_right,
spacing,
taskindex + nt_left,
f,
)
},
);
r1?;
r2?;
Ok(())
}
pub(crate) fn for_each_inner_block_with_offsets<F>(
dims: &[usize],
blocks: &[usize],
strides_list: &[Vec<isize>],
initial_offsets: &[isize],
f: F,
) -> Result<()>
where
F: FnMut(&[isize], usize, &[isize]) -> Result<()>,
{
for_each_inner_block_preordered(dims, blocks, strides_list, initial_offsets, f)
}
#[cfg(test)]
mod tests {
use super::*;
fn streaming_lastargmax(dims: &[usize], costs: &[isize]) -> usize {
let (i, _) = dims.iter().zip(costs.iter()).enumerate().fold(
(0, isize::MIN),
|(best_i, best_v), (idx, (&d, &c))| {
let score = (d as isize - 1) * c;
if score >= best_v {
(idx, score)
} else {
(best_i, best_v)
}
},
);
i
}
#[test]
fn test_streaming_lastargmax() {
assert_eq!(streaming_lastargmax(&[10, 20, 5], &[2, 1, 3]), 1);
assert_eq!(streaming_lastargmax(&[10, 10, 10], &[1, 1, 1]), 2);
assert_eq!(streaming_lastargmax(&[1, 1, 1], &[1, 1, 1]), 2);
assert_eq!(streaming_lastargmax(&[100], &[2]), 0);
}
#[test]
fn test_mapreduce_threaded_single_thread() {
let dims = vec![10, 10];
let blocks = vec![10, 10];
let strides = vec![vec![1isize, 10], vec![1, 10]];
let offsets = vec![0isize, 0];
let costs = vec![2, 20];
let called = std::sync::atomic::AtomicBool::new(false);
mapreduce_threaded(
&dims,
&blocks,
&strides,
&offsets,
&costs,
1,
0,
1,
&|_dims, _blocks, _strides, _offsets| {
called.store(true, std::sync::atomic::Ordering::SeqCst);
Ok(())
},
)
.unwrap();
assert!(called.load(std::sync::atomic::Ordering::SeqCst));
}
#[test]
fn test_mapreduce_threaded_splits_cover_all_elements() {
use std::sync::atomic::{AtomicUsize, Ordering};
let dims = vec![100, 100];
let blocks = vec![100, 100];
let strides = vec![vec![1isize, 100], vec![1, 100]];
let offsets = vec![0isize, 0];
let costs = vec![2, 200];
let total_elements = AtomicUsize::new(0);
mapreduce_threaded(
&dims,
&blocks,
&strides,
&offsets,
&costs,
4,
0,
1,
&|dims, _blocks, _strides, _offsets| {
let n: usize = dims.iter().product();
total_elements.fetch_add(n, Ordering::Relaxed);
Ok(())
},
)
.unwrap();
assert_eq!(total_elements.load(Ordering::SeqCst), 10000);
}
#[test]
fn test_mapreduce_threaded_with_spacing() {
use std::sync::atomic::{AtomicI64, Ordering};
let dims = vec![10];
let blocks = vec![10];
let strides = vec![vec![0isize], vec![1]];
let offsets = vec![0isize, 0];
let costs = vec![2];
let received_offset = AtomicI64::new(0);
mapreduce_threaded(
&dims,
&blocks,
&strides,
&offsets,
&costs,
1,
8,
3, &|_dims, _blocks, _strides, offsets| {
received_offset.store(offsets[0] as i64, Ordering::SeqCst);
Ok(())
},
)
.unwrap();
assert_eq!(received_offset.load(Ordering::SeqCst), 16);
}
}