1use crate::inline::InlineVec;
10use crate::kernel::for_each_inner_block_preordered;
11use crate::Result;
12use core::num::NonZeroUsize;
13use std::ops::Range;
14use strided_exec::Exec;
15
16type SVec<T> = InlineVec<T>;
19
20const ONE: NonZeroUsize = NonZeroUsize::MIN;
21
22pub struct SendPtr<T>(pub *mut T);
39
40impl<T> Clone for SendPtr<T> {
41 fn clone(&self) -> Self {
42 *self
43 }
44}
45
46impl<T> Copy for SendPtr<T> {}
47
48unsafe impl<T> Send for SendPtr<T> {}
49unsafe impl<T> Sync for SendPtr<T> {}
50
51impl<T> SendPtr<T> {
52 pub fn as_ptr(self) -> *mut T {
53 self.0
54 }
55
56 pub fn as_const(self) -> *const T {
57 self.0 as *const T
58 }
59}
60
61impl<T> core::fmt::Debug for SendPtr<T> {
62 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63 f.debug_tuple("SendPtr").field(&self.0).finish()
64 }
65}
66
67pub use strided_perm::MINTHREADLENGTH;
71
72fn scheduler_blocks(dims: &[usize], blocks: &[usize]) -> SVec<usize> {
81 dims.iter()
82 .zip(blocks)
83 .map(|(&dim, &block)| {
84 if dim > 1 && dim <= block.min(1024) {
85 (dim / 2).max(1)
86 } else {
87 block
88 }
89 })
90 .collect()
91}
92
93pub fn parallel_threads_for_len(len: usize, budget: usize) -> usize {
107 if len <= MINTHREADLENGTH {
108 1
109 } else {
110 budget.max(1)
111 }
112}
113
114pub(crate) fn for_len(exec: Exec<'_>, len: usize) -> Exec<'_> {
116 let width = parallel_threads_for_len(len, exec.budget().get());
117 exec.limit(NonZeroUsize::new(width).unwrap_or(ONE))
118}
119
120pub(crate) fn is_parallel(len: usize, exec: &Exec<'_>) -> bool {
122 len > MINTHREADLENGTH && !exec.is_serial()
123}
124
125fn fork2<R: Send>(
129 exec: Exec<'_>,
130 left: impl Fn(Exec<'_>) -> R + Sync,
131 right: impl Fn(Exec<'_>) -> R + Sync,
132) -> (R, R) {
133 struct Slot<T>(core::cell::UnsafeCell<Option<T>>);
136 unsafe impl<T: Send> Sync for Slot<T> {}
139 let slots = [Slot(Default::default()), Slot(Default::default())];
140 exec.fork(2, |i, child| {
141 let value = if i == 0 { left(child) } else { right(child) };
142 unsafe { *slots[i].0.get() = Some(value) };
144 });
145 let [left, right] = slots.map(|slot| {
146 slot.0
147 .into_inner()
148 .expect("every fork job stores its result")
149 });
150 (left, right)
151}
152
153pub fn parallel_map_reduce<R, Map, Reduce>(
170 range: Range<usize>,
171 exec: Exec<'_>,
172 map: &Map,
173 reduce: &Reduce,
174) -> R
175where
176 R: Send,
177 Map: Fn(Range<usize>) -> R + Sync,
178 Reduce: Fn(R, R) -> R + Sync,
179{
180 let len = range.end - range.start;
181 let budget = exec.budget().get();
182 if budget <= 1 || len <= 1 {
183 return map(range);
184 }
185
186 let left_threads = budget.div_ceil(2);
187 let left_len = len * left_threads / budget;
188 let middle = range.start + left_len.max(1).min(len - 1);
189 let left_range = range.start..middle;
190 let right_range = middle..range.end;
191
192 let (left, right) = fork2(
193 exec,
194 |child| parallel_map_reduce(left_range.clone(), child, map, reduce),
195 |child| parallel_map_reduce(right_range.clone(), child, map, reduce),
196 );
197 reduce(left, right)
198}
199
200pub(crate) fn parallel_for_each<F>(range: Range<usize>, exec: Exec<'_>, operation: &F)
203where
204 F: Fn(Range<usize>) + Sync,
205{
206 let start = range.start;
207 exec.for_ranges(range.end - range.start, ONE, |chunk| {
208 operation(start + chunk.start..start + chunk.end)
209 });
210}
211
212pub(crate) unsafe fn copy_contiguous<T: Copy + Send + Sync>(
222 src: *const T,
223 dst: *mut T,
224 len: usize,
225 exec: Exec<'_>,
226) {
227 if is_parallel(len, &exec) {
228 let src = SendPtr(src as *mut T);
229 let dst = SendPtr(dst);
230 parallel_for_each(0..len, exec, &|range| {
231 unsafe {
235 core::ptr::copy_nonoverlapping(
236 src.as_const().add(range.start),
237 dst.as_ptr().add(range.start),
238 range.len(),
239 );
240 }
241 });
242 return;
243 }
244 unsafe { core::ptr::copy_nonoverlapping(src, dst, len) };
246}
247
248pub(crate) fn fill_contiguous<T: Copy + Send + Sync>(dst: &mut [T], value: T, exec: Exec<'_>) {
251 let len = dst.len();
252 if is_parallel(len, &exec) {
253 let dst = SendPtr(dst.as_mut_ptr());
254 parallel_for_each(0..len, exec, &|range| {
255 let chunk = unsafe {
258 core::slice::from_raw_parts_mut(dst.as_ptr().add(range.start), range.len())
259 };
260 chunk.fill(value);
261 });
262 return;
263 }
264 dst.fill(value);
265}
266
267pub(crate) unsafe fn fill_contiguous_uninit<T: Copy + Send + Sync>(
279 dst: *mut T,
280 len: usize,
281 value: T,
282 exec: Exec<'_>,
283) {
284 let span =
287 unsafe { core::slice::from_raw_parts_mut(dst.cast::<core::mem::MaybeUninit<T>>(), len) };
288 fill_contiguous(span, core::mem::MaybeUninit::new(value), exec);
289}
290
291pub(crate) fn copy_permuted<T: Copy + Send + Sync + 'static>(
298 dest: &mut crate::StridedViewMut<T>,
299 src: &crate::StridedView<T>,
300 exec: Exec<'_>,
301) -> crate::Result<()> {
302 let total = crate::kernel::total_len(dest.dims())?;
303 strided_perm::copy_into(dest, src, for_len(exec, total))
304}
305
306pub(crate) fn copy_permuted_uninit<T: Copy + Send + Sync + 'static>(
313 dest: &mut crate::StridedViewMut<core::mem::MaybeUninit<T>>,
314 src: &crate::StridedView<T>,
315 exec: Exec<'_>,
316) -> crate::Result<()> {
317 let total = crate::kernel::total_len(dest.dims())?;
318 strided_perm::copy_into_uninit(dest, src, for_len(exec, total))
319}
320
321fn split_axis(dims: &[usize], blocks: &[usize], costs: &[isize]) -> Option<usize> {
328 let (i, _) = dims.iter().zip(costs.iter()).enumerate().fold(
329 (0, isize::MIN),
330 |(best_i, best_v), (idx, (&d, &c))| {
331 let score = (d as isize - 1) * c;
332 if score >= best_v {
333 (idx, score)
334 } else {
335 (best_i, best_v)
336 }
337 },
338 );
339 (costs[i] != 0 && dims[i] > blocks[i].min(1024)).then_some(i)
340}
341
342fn halves(
345 dims: &[usize],
346 offsets: &[isize],
347 strides_list: &[&[isize]],
348 i: usize,
349) -> (SVec<usize>, SVec<usize>, SVec<isize>) {
350 let di = dims[i];
351 let ndi = di / 2;
352 let mut left_dims: SVec<usize> = dims.iter().copied().collect();
353 left_dims[i] = ndi;
354 let mut right_dims: SVec<usize> = dims.iter().copied().collect();
355 right_dims[i] = di - ndi;
356 let mut right_offsets: SVec<isize> = offsets.iter().copied().collect();
357 for (k, strides) in strides_list.iter().enumerate() {
358 right_offsets[k] += ndi as isize * strides[i];
359 }
360 (left_dims, right_dims, right_offsets)
361}
362
363pub(crate) fn mapreduce_threaded<S: AsRef<[isize]>, F>(
380 dims: &[usize],
381 blocks: &[usize],
382 strides_list: &[S],
383 offsets: &[isize],
384 costs: &[isize],
385 exec: Exec<'_>,
386 f: &F,
387) -> Result<()>
388where
389 F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<()> + Sync,
390{
391 let refs: SVec<&[isize]> = strided_slices(strides_list);
396 mapreduce_threaded_dyn(dims, blocks, &refs, offsets, costs, exec, f)
397}
398
399fn strided_slices<S: AsRef<[isize]>>(strides_list: &[S]) -> SVec<&[isize]> {
400 strides_list.iter().map(|s| s.as_ref()).collect()
401}
402
403type LeafFn<'a> = &'a (dyn Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<()> + Sync);
405
406#[inline(never)]
407fn mapreduce_threaded_dyn(
408 dims: &[usize],
409 blocks: &[usize],
410 strides_list: &[&[isize]],
411 offsets: &[isize],
412 costs: &[isize],
413 exec: Exec<'_>,
414 f: LeafFn<'_>,
415) -> Result<()> {
416 let blocks = scheduler_blocks(dims, blocks);
421 ThreadedMapReduce {
422 blocks: &blocks,
423 strides_list,
424 costs,
425 operation: f,
426 }
427 .run(dims, offsets, exec)
428}
429
430struct ThreadedMapReduce<'a> {
431 blocks: &'a [usize],
432 strides_list: &'a [&'a [isize]],
433 costs: &'a [isize],
434 operation: LeafFn<'a>,
435}
436
437impl ThreadedMapReduce<'_> {
438 fn run(&self, dims: &[usize], offsets: &[isize], exec: Exec<'_>) -> Result<()> {
439 let total = crate::kernel::total_len(dims)?;
440
441 if exec.is_serial() || total <= MINTHREADLENGTH {
443 return (self.operation)(dims, self.blocks, self.strides_list, offsets);
444 }
445 let Some(i) = split_axis(dims, self.blocks, self.costs) else {
446 return (self.operation)(dims, self.blocks, self.strides_list, offsets);
447 };
448
449 let (left_dims, right_dims, right_offsets) = halves(dims, offsets, self.strides_list, i);
450 let (r1, r2) = fork2(
451 exec,
452 |child| self.run(&left_dims, offsets, child),
453 |child| self.run(&right_dims, &right_offsets, child),
454 );
455 r1?;
456 r2?;
457 Ok(())
458 }
459}
460
461#[allow(clippy::too_many_arguments)]
469pub(crate) fn mapreduce_threaded_fold<S, F, C, R>(
470 dims: &[usize],
471 blocks: &[usize],
472 strides_list: &[S],
473 offsets: &[isize],
474 costs: &[isize],
475 exec: Exec<'_>,
476 f: &F,
477 combine: &C,
478) -> Result<R>
479where
480 S: AsRef<[isize]>,
481 F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<R> + Sync,
482 C: Fn(R, R) -> R + Sync,
483 R: Send,
484{
485 let refs: SVec<&[isize]> = strided_slices(strides_list);
486 let blocks = scheduler_blocks(dims, blocks);
490 FoldMapReduce {
491 blocks: &blocks,
492 strides_list: &refs,
493 costs,
494 f,
495 combine,
496 }
497 .run(dims, offsets, exec)
498}
499
500struct FoldMapReduce<'a, F, C> {
501 blocks: &'a [usize],
502 strides_list: &'a [&'a [isize]],
503 costs: &'a [isize],
504 f: &'a F,
505 combine: &'a C,
506}
507
508impl<F, C> FoldMapReduce<'_, F, C> {
509 fn run<R: Send>(&self, dims: &[usize], offsets: &[isize], exec: Exec<'_>) -> Result<R>
510 where
511 F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<R> + Sync,
512 C: Fn(R, R) -> R + Sync,
513 {
514 let total = crate::kernel::total_len(dims)?;
515
516 if exec.is_serial() || total <= MINTHREADLENGTH {
518 return (self.f)(dims, self.blocks, self.strides_list, offsets);
519 }
520 let Some(i) = split_axis(dims, self.blocks, self.costs) else {
521 return (self.f)(dims, self.blocks, self.strides_list, offsets);
522 };
523
524 let (left_dims, right_dims, right_offsets) = halves(dims, offsets, self.strides_list, i);
526 let (r1, r2) = fork2(
527 exec,
528 |child| self.run(&left_dims, offsets, child),
529 |child| self.run(&right_dims, &right_offsets, child),
530 );
531 Ok((self.combine)(r1?, r2?))
532 }
533}
534
535pub(crate) fn for_each_inner_block_with_offsets<S: AsRef<[isize]>, F>(
541 dims: &[usize],
542 blocks: &[usize],
543 strides_list: &[S],
544 initial_offsets: &[isize],
545 f: F,
546) -> Result<()>
547where
548 F: FnMut(&[isize], usize, &[isize]) -> Result<()>,
549{
550 for_each_inner_block_preordered(dims, blocks, strides_list, initial_offsets, f)
551}
552
553#[cfg(test)]
555pub(crate) fn test_pool(threads: usize) -> strided_exec::Pool {
556 strided_exec::Pool::builder()
557 .threads(threads)
558 .build()
559 .unwrap()
560}
561
562#[cfg(test)]
570pub(crate) fn run_serial_and_parallel<R: Send>(operation: impl Fn(Exec<'_>) -> R + Sync) -> (R, R) {
571 let serial = operation(Exec::serial());
572 let pool = test_pool(4);
573 let exec = Exec::new(&pool, 4).unwrap();
574 assert_eq!(
575 parallel_threads_for_len(MINTHREADLENGTH + 1, exec.budget().get()),
576 exec.budget().get()
577 );
578 assert_eq!(parallel_threads_for_len(MINTHREADLENGTH, 4), 1);
579 let parallel = operation(exec);
580 (serial, parallel)
581}
582
583#[cfg(test)]
584#[path = "threading/tests/tests.rs"]
585mod tests;