#[cfg(feature = "parallel")]
use crate::inline::InlineVec;
#[cfg(feature = "parallel")]
use std::ops::Range;
#[cfg(feature = "parallel")]
use core::num::NonZeroUsize;
#[cfg(feature = "parallel")]
use crate::kernel::for_each_inner_block_preordered;
#[cfg(feature = "parallel")]
use crate::Result;
#[cfg(feature = "parallel")]
type SVec<T> = InlineVec<T>;
#[cfg(feature = "parallel")]
pub struct SendPtr<T>(pub *mut T);
#[cfg(feature = "parallel")]
impl<T> Clone for SendPtr<T> {
fn clone(&self) -> Self {
*self
}
}
#[cfg(feature = "parallel")]
impl<T> Copy for SendPtr<T> {}
#[cfg(feature = "parallel")]
unsafe impl<T> Send for SendPtr<T> {}
#[cfg(feature = "parallel")]
unsafe impl<T> Sync for SendPtr<T> {}
#[cfg(feature = "parallel")]
impl<T> SendPtr<T> {
pub fn as_ptr(self) -> *mut T {
self.0
}
pub fn as_const(self) -> *const T {
self.0 as *const T
}
}
#[cfg(feature = "parallel")]
pub const MINTHREADLENGTH: usize = 1 << 15;
#[cfg(feature = "parallel")]
fn scheduler_blocks(dims: &[usize], blocks: &[usize]) -> SVec<usize> {
dims.iter()
.zip(blocks)
.map(|(&dim, &block)| {
if dim > 1 && dim <= block.min(1024) {
(dim / 2).max(1)
} else {
block
}
})
.collect()
}
#[cfg(feature = "parallel")]
pub fn parallel_threads_for_len(len: usize) -> usize {
if len <= MINTHREADLENGTH {
return 1;
}
let nthreads = crate::execution_policy::rayon_threads();
if nthreads > 1 {
nthreads
} else {
1
}
}
#[cfg(feature = "parallel")]
fn join_with_policy<A, B, RA, RB>(policy: crate::ExecutionPolicy, left: A, right: B) -> (RA, RB)
where
A: FnOnce() -> RA + Send,
B: FnOnce() -> RB + Send,
RA: Send,
RB: Send,
{
let run = || match policy {
crate::ExecutionPolicy::AmbientRayon => rayon::join(left, right),
crate::ExecutionPolicy::Sequential => (
crate::execution_policy::with_owned_execution(policy, false, left),
crate::execution_policy::with_owned_execution(policy, false, right),
),
crate::ExecutionPolicy::Rayon { .. } => {
crate::execution_policy::with_scheduler_suspended(|| {
rayon::join(
|| crate::execution_policy::with_owned_execution(policy, false, left),
|| crate::execution_policy::with_owned_execution(policy, false, right),
)
})
}
};
if matches!(policy, crate::ExecutionPolicy::Sequential) {
run()
} else {
enter_target_pool(run)
}
}
#[cfg(feature = "parallel")]
pub fn parallel_map_reduce<R, Map, Reduce>(
range: Range<usize>,
nthreads: usize,
map: &Map,
reduce: &Reduce,
) -> R
where
R: Send,
Map: Fn(Range<usize>) -> R + Sync,
Reduce: Fn(R, R) -> R + Sync,
{
let policy = crate::execution_policy::active_policy();
parallel_map_reduce_with_policy(
range,
crate::execution_policy::clamp_fanout_width(nthreads, policy),
policy,
false,
map,
reduce,
)
}
#[cfg(feature = "parallel")]
pub(crate) fn parallel_for_each<F>(range: Range<usize>, nthreads: usize, operation: &F)
where
F: Fn(Range<usize>) + Sync,
{
parallel_map_reduce(
range,
nthreads,
&|subrange| operation(subrange),
&|(), ()| (),
);
}
pub(crate) unsafe fn copy_contiguous<T: Copy + crate::MaybeSendSync>(
src: *const T,
dst: *mut T,
len: usize,
) {
#[cfg(feature = "parallel")]
{
let nthreads = parallel_threads_for_len(len);
if nthreads > 1 {
let src = SendPtr(src as *mut T);
let dst = SendPtr(dst);
parallel_for_each(0..len, nthreads, &|range| {
unsafe {
core::ptr::copy_nonoverlapping(
src.as_const().add(range.start),
dst.as_ptr().add(range.start),
range.len(),
);
}
});
return;
}
}
unsafe { core::ptr::copy_nonoverlapping(src, dst, len) };
}
pub(crate) fn fill_contiguous<T: Copy + crate::MaybeSendSync>(dst: &mut [T], value: T) {
#[cfg(feature = "parallel")]
{
let len = dst.len();
let nthreads = parallel_threads_for_len(len);
if nthreads > 1 {
let dst = SendPtr(dst.as_mut_ptr());
parallel_for_each(0..len, nthreads, &|range| {
let chunk = unsafe {
core::slice::from_raw_parts_mut(dst.as_ptr().add(range.start), range.len())
};
chunk.fill(value);
});
return;
}
}
dst.fill(value);
}
pub(crate) unsafe fn fill_contiguous_uninit<T: Copy + crate::MaybeSendSync>(
dst: *mut T,
len: usize,
value: T,
) {
let span =
unsafe { core::slice::from_raw_parts_mut(dst.cast::<core::mem::MaybeUninit<T>>(), len) };
fill_contiguous(span, core::mem::MaybeUninit::new(value));
}
pub(crate) fn copy_into_col_major<T: Copy + crate::MaybeSendSync + 'static>(
dest: &mut crate::StridedViewMut<T>,
src: &crate::StridedView<T>,
) -> crate::Result<()> {
strided_perm::copy_into_col_major(dest, src)
}
pub(crate) fn copy_permuted_serial<T: Copy + crate::MaybeSendSync + 'static>(
dest: &mut crate::StridedViewMut<T>,
src: &crate::StridedView<T>,
) -> crate::Result<()> {
strided_perm::copy_into(dest, src)
}
#[cfg(test)]
#[path = "threading/tests/default_tests.rs"]
mod default_tests;
#[cfg(feature = "parallel")]
pub(crate) fn current_pool_threads() -> usize {
rayon::current_num_threads()
}
#[cfg(feature = "parallel")]
#[derive(Clone, Copy)]
struct TargetPool {
pool: *const rayon::ThreadPool,
max_threads: NonZeroUsize,
}
#[cfg(feature = "parallel")]
std::thread_local! {
static TARGET_POOL: core::cell::Cell<Option<TargetPool>> = const { core::cell::Cell::new(None) };
}
#[cfg(all(test, feature = "parallel"))]
std::thread_local! {
static TARGET_POOL_INSTALLS: core::cell::Cell<usize> = const { core::cell::Cell::new(0) };
}
#[cfg(all(test, feature = "parallel"))]
pub(crate) fn target_pool_install_count() -> usize {
TARGET_POOL_INSTALLS.with(core::cell::Cell::get)
}
#[cfg(all(test, feature = "parallel"))]
pub(crate) fn reset_target_pool_install_count() {
TARGET_POOL_INSTALLS.with(|count| count.set(0));
}
#[cfg(all(test, feature = "parallel"))]
static PERMUTATION_FALLBACK_WORKERS: std::sync::Mutex<Vec<Option<String>>> =
std::sync::Mutex::new(Vec::new());
#[cfg(all(test, feature = "parallel"))]
fn record_permutation_fallback_worker() {
let name = std::thread::current().name().map(str::to_owned);
let mut workers = PERMUTATION_FALLBACK_WORKERS.lock().unwrap();
if workers.len() < 32 && !workers.iter().any(|entry| *entry == name) {
workers.push(name);
}
}
#[cfg(all(test, feature = "parallel"))]
pub(crate) fn take_permutation_fallback_workers() -> Vec<Option<String>> {
std::mem::take(&mut *PERMUTATION_FALLBACK_WORKERS.lock().unwrap())
}
#[cfg(feature = "parallel")]
struct TargetPoolGuard {
previous: Option<TargetPool>,
}
#[cfg(feature = "parallel")]
impl Drop for TargetPoolGuard {
fn drop(&mut self) {
set_target_pool(self.previous);
}
}
#[cfg(feature = "parallel")]
fn target_pool() -> Option<TargetPool> {
TARGET_POOL.with(core::cell::Cell::get)
}
#[cfg(feature = "parallel")]
fn set_target_pool(pool: Option<TargetPool>) {
TARGET_POOL.with(|cell| cell.set(pool));
}
#[cfg(feature = "parallel")]
pub(crate) fn with_execution_pool<R>(
pool: &rayon::ThreadPool,
max_threads: NonZeroUsize,
operation: impl FnOnce() -> R,
) -> R {
let previous = target_pool();
let max_threads = previous
.map(|outer| outer.max_threads.min(max_threads))
.unwrap_or(max_threads);
set_target_pool(Some(TargetPool {
pool: pool as *const _,
max_threads,
}));
let _guard = TargetPoolGuard { previous };
operation()
}
#[cfg(feature = "parallel")]
pub(crate) fn with_target_pool_suspended<R>(operation: impl FnOnce() -> R) -> R {
let previous = target_pool();
set_target_pool(None);
let _guard = TargetPoolGuard { previous };
operation()
}
#[cfg(feature = "parallel")]
pub(crate) fn target_pool_width() -> Option<usize> {
target_pool().map(|target| {
let pool = unsafe { &*target.pool };
target.max_threads.get().min(pool.current_num_threads())
})
}
#[cfg(feature = "parallel")]
pub(crate) fn target_pool_physical_threads() -> Option<usize> {
target_pool().map(|target| {
let pool = unsafe { &*target.pool };
pool.current_num_threads()
})
}
#[cfg(feature = "parallel")]
pub(crate) fn enter_target_pool<R: Send>(operation: impl FnOnce() -> R + Send) -> R {
let Some(target) = target_pool() else {
return operation();
};
let pool = unsafe { &*target.pool };
let width = target.max_threads.get().min(pool.current_num_threads());
if width <= 1 || pool.current_thread_index().is_some() {
operation()
} else {
#[cfg(all(test, feature = "parallel"))]
TARGET_POOL_INSTALLS.with(|count| count.set(count.get() + 1));
crate::execution_policy::with_scheduler_suspended(|| pool.install(operation))
}
}
#[cfg(feature = "parallel")]
pub(crate) fn run_on_pool<T: Send>(
pool: &rayon::ThreadPool,
budget: core::num::NonZeroUsize,
operation: impl FnOnce(usize) -> T + Send,
) -> T {
let width = budget.get().min(pool.current_num_threads());
let run = move || operation(width);
if width <= 1 || pool.current_thread_index().is_some() {
run()
} else {
pool.install(run)
}
}
#[cfg(feature = "parallel")]
fn with_permutation_copy_scheduler<R>(operation: impl FnOnce() -> R) -> R {
crate::execution_policy::with_scheduler_suspended(operation)
}
#[cfg(feature = "parallel")]
pub(crate) fn copy_permuted_with_active_policy<T: Copy + crate::MaybeSendSync + 'static>(
dest: &mut crate::StridedViewMut<T>,
src: &crate::StridedView<T>,
) -> crate::Result<()> {
let policy = crate::execution_policy::active_policy();
let total = crate::kernel::total_len(dest.dims())?;
if policy == crate::ExecutionPolicy::Sequential {
return copy_permuted_serial(dest, src);
}
let physical_pool_threads = target_pool_physical_threads().unwrap_or_else(current_pool_threads);
let effective_pool_threads = target_pool_width().unwrap_or(physical_pool_threads);
let parallel_eligible = crate::execution_policy::permutation_copy_parallel_eligible(
policy,
crate::execution_policy::fanout_active(),
physical_pool_threads,
);
if total > MINTHREADLENGTH && parallel_eligible {
return enter_target_pool(|| {
with_permutation_copy_scheduler(|| strided_perm::copy_into_par(dest, src))
});
}
if total > MINTHREADLENGTH && effective_pool_threads > 1 {
return crate::map_view::map_into(dest, src, |value| {
#[cfg(all(test, feature = "parallel"))]
record_permutation_fallback_worker();
value
});
}
copy_permuted_serial(dest, src)
}
#[cfg(feature = "parallel")]
fn parallel_map_reduce_with_policy<R, Map, Reduce>(
range: Range<usize>,
nthreads: usize,
policy: crate::ExecutionPolicy,
in_fanout: bool,
map: &Map,
reduce: &Reduce,
) -> R
where
R: Send,
Map: Fn(Range<usize>) -> R + Sync,
Reduce: Fn(R, R) -> R + Sync,
{
let len = range.end - range.start;
if nthreads <= 1 || len <= 1 {
return crate::execution_policy::with_owned_execution(policy, in_fanout, || map(range));
}
let left_threads = nthreads / 2;
let right_threads = nthreads - left_threads;
let left_len = len * left_threads / nthreads;
let middle = range.start + left_len.max(1).min(len - 1);
let left_range = range.start..middle;
let right_range = middle..range.end;
let (left, right) = join_with_policy(
policy,
|| parallel_map_reduce_with_policy(left_range, left_threads, policy, true, map, reduce),
|| parallel_map_reduce_with_policy(right_range, right_threads, policy, true, map, reduce),
);
crate::execution_policy::with_owned_execution(policy, true, || reduce(left, right))
}
#[cfg(feature = "parallel")]
pub(crate) fn mapreduce_threaded<S: AsRef<[isize]>, F>(
dims: &[usize],
blocks: &[usize],
strides_list: &[S],
offsets: &[isize],
costs: &[isize],
nthreads: usize,
spacing: isize,
taskindex: usize,
f: &F,
) -> Result<()>
where
F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<()> + Sync,
{
let refs: SVec<&[isize]> = strides_list.iter().map(|s| s.as_ref()).collect();
let nthreads = crate::execution_policy::clamp_fanout_width(
nthreads,
crate::execution_policy::active_policy(),
);
mapreduce_threaded_dyn(
dims, blocks, &refs, offsets, costs, nthreads, spacing, taskindex, f,
)
}
#[cfg(feature = "parallel")]
type LeafFn<'a> = &'a (dyn Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<()> + Sync);
#[cfg(feature = "parallel")]
#[inline(never)]
fn mapreduce_threaded_dyn(
dims: &[usize],
blocks: &[usize],
strides_list: &[&[isize]],
offsets: &[isize],
costs: &[isize],
nthreads: usize,
spacing: isize,
taskindex: usize,
f: LeafFn<'_>,
) -> Result<()> {
let blocks = scheduler_blocks(dims, blocks);
ThreadedMapReduce {
blocks: &blocks,
strides_list,
costs,
spacing,
policy: crate::execution_policy::active_policy(),
operation: f,
}
.run(dims, offsets, nthreads, taskindex, false)
}
#[cfg(feature = "parallel")]
struct ThreadedMapReduce<'a> {
blocks: &'a [usize],
strides_list: &'a [&'a [isize]],
costs: &'a [isize],
spacing: isize,
policy: crate::ExecutionPolicy,
operation: LeafFn<'a>,
}
#[cfg(feature = "parallel")]
impl ThreadedMapReduce<'_> {
fn run(
&self,
dims: &[usize],
offsets: &[isize],
nthreads: usize,
taskindex: usize,
in_fanout: bool,
) -> Result<()> {
let total = crate::kernel::total_len(dims)?;
if nthreads <= 1 || total <= MINTHREADLENGTH {
if self.spacing != 0 {
let mut spaced: SVec<isize> = offsets.iter().copied().collect();
spaced[0] += self.spacing * (taskindex as isize - 1);
return crate::execution_policy::with_owned_execution(
self.policy,
in_fanout,
|| (self.operation)(dims, self.blocks, self.strides_list, &spaced),
);
}
return crate::execution_policy::with_owned_execution(self.policy, in_fanout, || {
(self.operation)(dims, self.blocks, self.strides_list, offsets)
});
}
let (i, _) = dims.iter().zip(self.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 self.costs[i] == 0 || dims[i] <= self.blocks[i].min(1024) {
if self.spacing != 0 {
let mut spaced: SVec<isize> = offsets.iter().copied().collect();
spaced[0] += self.spacing * (taskindex as isize - 1);
return crate::execution_policy::with_owned_execution(
self.policy,
in_fanout,
|| (self.operation)(dims, self.blocks, self.strides_list, &spaced),
);
}
return crate::execution_policy::with_owned_execution(self.policy, in_fanout, || {
(self.operation)(dims, self.blocks, self.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> = dims.iter().copied().collect();
left_dims[i] = ndi;
let mut right_dims: SVec<usize> = dims.iter().copied().collect();
right_dims[i] = di - ndi;
let mut right_offsets: SVec<isize> = offsets.iter().copied().collect();
for (k, strides) in self.strides_list.iter().enumerate() {
right_offsets[k] += ndi as isize * strides[i];
}
let left_offsets: SVec<isize> = offsets.iter().copied().collect();
let (r1, r2) = join_with_policy(
self.policy,
|| self.run(&left_dims, &left_offsets, nt_left, taskindex, true),
|| {
self.run(
&right_dims,
&right_offsets,
nt_right,
taskindex + nt_left,
true,
)
},
);
r1?;
r2?;
Ok(())
}
}
#[cfg(feature = "parallel")]
pub(crate) fn mapreduce_threaded_fold<S, F, C, R>(
dims: &[usize],
blocks: &[usize],
strides_list: &[S],
offsets: &[isize],
costs: &[isize],
nthreads: usize,
f: &F,
combine: &C,
) -> Result<R>
where
S: AsRef<[isize]>,
F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<R> + Sync,
C: Fn(R, R) -> R + Sync,
R: Send,
{
let refs: SVec<&[isize]> = strides_list.iter().map(|s| s.as_ref()).collect();
let policy = crate::execution_policy::active_policy();
let nthreads = crate::execution_policy::clamp_fanout_width(nthreads, policy);
let blocks = scheduler_blocks(dims, blocks);
FoldMapReduce {
blocks: &blocks,
strides_list: &refs,
costs,
policy,
f,
combine,
}
.run(dims, offsets, nthreads, false)
}
#[cfg(feature = "parallel")]
struct FoldMapReduce<'a, F, C> {
blocks: &'a [usize],
strides_list: &'a [&'a [isize]],
costs: &'a [isize],
policy: crate::ExecutionPolicy,
f: &'a F,
combine: &'a C,
}
#[cfg(feature = "parallel")]
impl<F, C> FoldMapReduce<'_, F, C> {
fn run<R: Send>(
&self,
dims: &[usize],
offsets: &[isize],
nthreads: usize,
in_fanout: bool,
) -> Result<R>
where
F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<R> + Sync,
C: Fn(R, R) -> R + Sync,
{
let total = crate::kernel::total_len(dims)?;
if nthreads <= 1 || total <= MINTHREADLENGTH {
return crate::execution_policy::with_owned_execution(self.policy, in_fanout, || {
(self.f)(dims, self.blocks, self.strides_list, offsets)
});
}
let (i, _) = dims.iter().zip(self.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 self.costs[i] == 0 || dims[i] <= self.blocks[i].min(1024) {
return crate::execution_policy::with_owned_execution(self.policy, in_fanout, || {
(self.f)(dims, self.blocks, self.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> = dims.iter().copied().collect();
left_dims[i] = ndi;
let mut right_dims: SVec<usize> = dims.iter().copied().collect();
right_dims[i] = di - ndi;
let mut right_offsets: SVec<isize> = offsets.iter().copied().collect();
for (k, strides) in self.strides_list.iter().enumerate() {
right_offsets[k] += ndi as isize * strides[i];
}
let left_offsets: SVec<isize> = offsets.iter().copied().collect();
let (r1, r2) = join_with_policy(
self.policy,
|| self.run(&left_dims, &left_offsets, nt_left, true),
|| self.run(&right_dims, &right_offsets, nt_right, true),
);
Ok((self.combine)(r1?, r2?))
}
}
#[cfg(feature = "parallel")]
pub(crate) fn for_each_inner_block_with_offsets<S: AsRef<[isize]>, F>(
dims: &[usize],
blocks: &[usize],
strides_list: &[S],
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(all(test, feature = "parallel"))]
pub(crate) fn test_pool(threads: usize) -> std::sync::Arc<rayon::ThreadPool> {
std::sync::Arc::new(
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap(),
)
}
#[cfg(all(test, feature = "parallel"))]
pub(crate) fn run_serial_and_parallel<R: Send>(operation: impl Fn() -> R + Sync) -> (R, R) {
let serial = crate::ExecContext::serial().run(|| {
assert_eq!(parallel_threads_for_len(MINTHREADLENGTH + 1), 1);
operation()
});
let parallel = test_pool(4).install(|| {
crate::ExecContext::max_threads(4).unwrap().run(|| {
assert_eq!(parallel_threads_for_len(MINTHREADLENGTH + 1), 4);
assert_eq!(parallel_threads_for_len(MINTHREADLENGTH), 1);
operation()
})
});
(serial, parallel)
}
#[cfg(all(test, feature = "parallel"))]
#[path = "threading/tests/tests.rs"]
mod tests;
#[cfg(feature = "parallel")]
impl<T> core::fmt::Debug for SendPtr<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("SendPtr").field(&self.0).finish()
}
}