1use crate::cost::KernelClass;
10pub(crate) use crate::cost::{for_len, is_parallel};
11use crate::inline::InlineVec;
12use crate::kernel::for_each_inner_block_preordered;
13use crate::Result;
14use core::num::NonZeroUsize;
15use std::ops::Range;
16use threadteam::Exec;
17
18type SVec<T> = InlineVec<T>;
21
22const ONE: NonZeroUsize = NonZeroUsize::MIN;
23
24pub struct SendPtr<T>(pub *mut T);
41
42impl<T> Clone for SendPtr<T> {
43 fn clone(&self) -> Self {
44 *self
45 }
46}
47
48impl<T> Copy for SendPtr<T> {}
49
50unsafe impl<T> Send for SendPtr<T> {}
51unsafe impl<T> Sync for SendPtr<T> {}
52
53impl<T> SendPtr<T> {
54 pub fn as_ptr(self) -> *mut T {
55 self.0
56 }
57
58 pub fn as_const(self) -> *const T {
59 self.0 as *const T
60 }
61}
62
63impl<T> core::fmt::Debug for SendPtr<T> {
64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
65 f.debug_tuple("SendPtr").field(&self.0).finish()
66 }
67}
68
69fn scheduler_blocks(dims: &[usize], blocks: &[usize]) -> SVec<usize> {
78 dims.iter()
79 .zip(blocks)
80 .map(|(&dim, &block)| {
81 if dim > 1 && dim <= block.min(1024) {
82 (dim / 2).max(1)
83 } else {
84 block
85 }
86 })
87 .collect()
88}
89
90fn fork2<R: Send>(
94 exec: Exec<'_>,
95 left: impl Fn(Exec<'_>) -> R + Sync,
96 right: impl Fn(Exec<'_>) -> R + Sync,
97) -> (R, R) {
98 struct Slot<T>(core::cell::UnsafeCell<Option<T>>);
101 unsafe impl<T: Send> Sync for Slot<T> {}
104 let slots = [Slot(Default::default()), Slot(Default::default())];
105 exec.fork(2, |i, child| {
106 let value = if i == 0 { left(child) } else { right(child) };
107 unsafe { *slots[i].0.get() = Some(value) };
109 });
110 let [left, right] = slots.map(|slot| {
111 slot.0
112 .into_inner()
113 .expect("every fork job stores its result")
114 });
115 (left, right)
116}
117
118pub fn parallel_map_reduce<R, Map, Reduce>(
135 range: Range<usize>,
136 exec: Exec<'_>,
137 map: &Map,
138 reduce: &Reduce,
139) -> R
140where
141 R: Send,
142 Map: Fn(Range<usize>) -> R + Sync,
143 Reduce: Fn(R, R) -> R + Sync,
144{
145 let len = range.end - range.start;
146 let budget = exec.budget().get();
147 if budget <= 1 || len <= 1 {
148 return map(range);
149 }
150
151 let left_threads = budget.div_ceil(2);
152 let left_len = len * left_threads / budget;
153 let middle = range.start + left_len.max(1).min(len - 1);
154 let left_range = range.start..middle;
155 let right_range = middle..range.end;
156
157 let (left, right) = fork2(
158 exec,
159 |child| parallel_map_reduce(left_range.clone(), child, map, reduce),
160 |child| parallel_map_reduce(right_range.clone(), child, map, reduce),
161 );
162 reduce(left, right)
163}
164
165pub(crate) fn parallel_for_each<F>(range: Range<usize>, exec: Exec<'_>, operation: &F)
168where
169 F: Fn(Range<usize>) + Sync,
170{
171 let start = range.start;
172 exec.for_ranges(range.end - range.start, ONE, |chunk| {
173 operation(start + chunk.start..start + chunk.end)
174 });
175}
176
177pub(crate) unsafe fn copy_contiguous<T: Copy + Send + Sync>(
187 src: *const T,
188 dst: *mut T,
189 len: usize,
190 exec: Exec<'_>,
191) {
192 let exec = for_len(exec, KernelClass::Copy, len);
193 if !exec.is_serial() {
194 let src = SendPtr(src as *mut T);
195 let dst = SendPtr(dst);
196 parallel_for_each(0..len, exec, &|range| {
197 unsafe {
201 core::ptr::copy_nonoverlapping(
202 src.as_const().add(range.start),
203 dst.as_ptr().add(range.start),
204 range.len(),
205 );
206 }
207 });
208 return;
209 }
210 unsafe { core::ptr::copy_nonoverlapping(src, dst, len) };
212}
213
214pub(crate) fn fill_contiguous<T: Copy + Send + Sync>(dst: &mut [T], value: T, exec: Exec<'_>) {
217 let len = dst.len();
218 let exec = for_len(exec, KernelClass::Copy, len);
219 if !exec.is_serial() {
220 let dst = SendPtr(dst.as_mut_ptr());
221 parallel_for_each(0..len, exec, &|range| {
222 let chunk = unsafe {
225 core::slice::from_raw_parts_mut(dst.as_ptr().add(range.start), range.len())
226 };
227 chunk.fill(value);
228 });
229 return;
230 }
231 dst.fill(value);
232}
233
234pub(crate) unsafe fn fill_contiguous_uninit<T: Copy + Send + Sync>(
246 dst: *mut T,
247 len: usize,
248 value: T,
249 exec: Exec<'_>,
250) {
251 let span =
254 unsafe { core::slice::from_raw_parts_mut(dst.cast::<core::mem::MaybeUninit<T>>(), len) };
255 fill_contiguous(span, core::mem::MaybeUninit::new(value), exec);
256}
257
258pub(crate) fn copy_permuted<T: Copy + Send + Sync + 'static>(
264 dest: &mut crate::StridedViewMut<T>,
265 src: &crate::StridedView<T>,
266 exec: Exec<'_>,
267) -> crate::Result<()> {
268 strided_perm::copy_into(dest, src, exec)
269}
270
271pub(crate) fn copy_permuted_uninit<T: Copy + Send + Sync + 'static>(
278 dest: &mut crate::StridedViewMut<core::mem::MaybeUninit<T>>,
279 src: &crate::StridedView<T>,
280 exec: Exec<'_>,
281) -> crate::Result<()> {
282 strided_perm::copy_into_uninit(dest, src, exec)
283}
284
285fn split_axis(dims: &[usize], blocks: &[usize], costs: &[isize]) -> Option<usize> {
292 let (i, _) = dims.iter().zip(costs.iter()).enumerate().fold(
293 (0, isize::MIN),
294 |(best_i, best_v), (idx, (&d, &c))| {
295 let score = (d as isize - 1) * c;
296 if score >= best_v {
297 (idx, score)
298 } else {
299 (best_i, best_v)
300 }
301 },
302 );
303 (costs[i] != 0 && dims[i] > blocks[i].min(1024)).then_some(i)
304}
305
306fn halves(
309 dims: &[usize],
310 offsets: &[isize],
311 strides_list: &[&[isize]],
312 i: usize,
313) -> (SVec<usize>, SVec<usize>, SVec<isize>) {
314 let di = dims[i];
315 let ndi = di / 2;
316 let mut left_dims: SVec<usize> = dims.iter().copied().collect();
317 left_dims[i] = ndi;
318 let mut right_dims: SVec<usize> = dims.iter().copied().collect();
319 right_dims[i] = di - ndi;
320 let mut right_offsets: SVec<isize> = offsets.iter().copied().collect();
321 for (k, strides) in strides_list.iter().enumerate() {
322 right_offsets[k] += ndi as isize * strides[i];
323 }
324 (left_dims, right_dims, right_offsets)
325}
326
327pub(crate) fn mapreduce_threaded<S: AsRef<[isize]>, F>(
344 dims: &[usize],
345 blocks: &[usize],
346 strides_list: &[S],
347 offsets: &[isize],
348 costs: &[isize],
349 class: KernelClass,
350 exec: Exec<'_>,
351 f: &F,
352) -> Result<()>
353where
354 F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<()> + Sync,
355{
356 let refs: SVec<&[isize]> = strided_slices(strides_list);
361 mapreduce_threaded_dyn(dims, blocks, &refs, offsets, costs, class, exec, f)
362}
363
364fn strided_slices<S: AsRef<[isize]>>(strides_list: &[S]) -> SVec<&[isize]> {
365 strides_list.iter().map(|s| s.as_ref()).collect()
366}
367
368type LeafFn<'a> = &'a (dyn Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<()> + Sync);
370
371#[inline(never)]
372fn mapreduce_threaded_dyn(
373 dims: &[usize],
374 blocks: &[usize],
375 strides_list: &[&[isize]],
376 offsets: &[isize],
377 costs: &[isize],
378 class: KernelClass,
379 exec: Exec<'_>,
380 f: LeafFn<'_>,
381) -> Result<()> {
382 let blocks = scheduler_blocks(dims, blocks);
387 ThreadedMapReduce {
388 blocks: &blocks,
389 strides_list,
390 costs,
391 class,
392 operation: f,
393 }
394 .run(dims, offsets, exec)
395}
396
397struct ThreadedMapReduce<'a> {
398 blocks: &'a [usize],
399 strides_list: &'a [&'a [isize]],
400 costs: &'a [isize],
401 class: KernelClass,
402 operation: LeafFn<'a>,
403}
404
405impl ThreadedMapReduce<'_> {
406 fn run(&self, dims: &[usize], offsets: &[isize], exec: Exec<'_>) -> Result<()> {
407 let total = crate::kernel::total_len(dims)?;
408
409 if !is_parallel(self.class, total, &exec) {
411 return (self.operation)(dims, self.blocks, self.strides_list, offsets);
412 }
413 let Some(i) = split_axis(dims, self.blocks, self.costs) else {
414 return (self.operation)(dims, self.blocks, self.strides_list, offsets);
415 };
416
417 let (left_dims, right_dims, right_offsets) = halves(dims, offsets, self.strides_list, i);
418 let (r1, r2) = fork2(
419 exec,
420 |child| self.run(&left_dims, offsets, child),
421 |child| self.run(&right_dims, &right_offsets, child),
422 );
423 r1?;
424 r2?;
425 Ok(())
426 }
427}
428
429#[allow(clippy::too_many_arguments)]
437pub(crate) fn mapreduce_threaded_fold<S, F, C, R>(
438 dims: &[usize],
439 blocks: &[usize],
440 strides_list: &[S],
441 offsets: &[isize],
442 costs: &[isize],
443 class: KernelClass,
444 exec: Exec<'_>,
445 f: &F,
446 combine: &C,
447) -> Result<R>
448where
449 S: AsRef<[isize]>,
450 F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<R> + Sync,
451 C: Fn(R, R) -> R + Sync,
452 R: Send,
453{
454 let refs: SVec<&[isize]> = strided_slices(strides_list);
455 let blocks = scheduler_blocks(dims, blocks);
459 FoldMapReduce {
460 blocks: &blocks,
461 strides_list: &refs,
462 costs,
463 class,
464 f,
465 combine,
466 }
467 .run(dims, offsets, exec)
468}
469
470struct FoldMapReduce<'a, F, C> {
471 blocks: &'a [usize],
472 strides_list: &'a [&'a [isize]],
473 costs: &'a [isize],
474 class: KernelClass,
475 f: &'a F,
476 combine: &'a C,
477}
478
479impl<F, C> FoldMapReduce<'_, F, C> {
480 fn run<R: Send>(&self, dims: &[usize], offsets: &[isize], exec: Exec<'_>) -> Result<R>
481 where
482 F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<R> + Sync,
483 C: Fn(R, R) -> R + Sync,
484 {
485 let total = crate::kernel::total_len(dims)?;
486
487 if !is_parallel(self.class, total, &exec) {
489 return (self.f)(dims, self.blocks, self.strides_list, offsets);
490 }
491 let Some(i) = split_axis(dims, self.blocks, self.costs) else {
492 return (self.f)(dims, self.blocks, self.strides_list, offsets);
493 };
494
495 let (left_dims, right_dims, right_offsets) = halves(dims, offsets, self.strides_list, i);
497 let (r1, r2) = fork2(
498 exec,
499 |child| self.run(&left_dims, offsets, child),
500 |child| self.run(&right_dims, &right_offsets, child),
501 );
502 Ok((self.combine)(r1?, r2?))
503 }
504}
505
506pub(crate) fn for_each_inner_block_with_offsets<S: AsRef<[isize]>, F>(
512 dims: &[usize],
513 blocks: &[usize],
514 strides_list: &[S],
515 initial_offsets: &[isize],
516 f: F,
517) -> Result<()>
518where
519 F: FnMut(&[isize], usize, &[isize]) -> Result<()>,
520{
521 for_each_inner_block_preordered(dims, blocks, strides_list, initial_offsets, f)
522}
523
524#[cfg(test)]
526pub(crate) fn test_pool(threads: usize) -> threadteam::Pool {
527 threadteam::Pool::builder()
528 .threads(threads)
529 .dispatch_costs(test_dispatch_costs())
530 .build()
531 .unwrap()
532}
533
534#[cfg(test)]
538pub(crate) fn test_dispatch_costs() -> threadteam::DispatchCosts {
539 use threadteam::{DispatchKind, WidthCosts};
540 let cheap = WidthCosts::flat(core::time::Duration::from_nanos(100));
541 threadteam::DispatchCosts::for_class(threadteam::HostClass::Generic)
542 .with(DispatchKind::Team, cheap)
543 .with(DispatchKind::InPool, cheap)
544 .with(DispatchKind::Outside, cheap)
545}
546
547#[cfg(test)]
555pub(crate) fn run_serial_and_parallel<R: Send>(operation: impl Fn(Exec<'_>) -> R + Sync) -> (R, R) {
556 let serial = operation(Exec::serial());
557 let pool = test_pool(4);
558 let exec = Exec::new(&pool, 4).unwrap();
559 assert_eq!(
560 crate::cost::parallel_width(&exec, KernelClass::Map, 1 << 20).get(),
561 exec.budget().get()
562 );
563 let parallel = operation(exec);
564 (serial, parallel)
565}
566
567#[cfg(test)]
568#[path = "threading/tests/tests.rs"]
569mod tests;