1use crate::kernel::{
4 build_plan_fused, ensure_same_shape, for_each_inner_block_preordered, same_contiguous_layout,
5 sequential_contiguous_layout, total_len,
6};
7use crate::simd;
8use crate::view::{col_major_strides, StridedArray, StridedView, StridedViewMut};
9use crate::{Exec, Result, StridedError};
10use core::num::NonZeroUsize;
11use std::ops::Range;
12use strided_view::ElementOp;
13
14use crate::cost::KernelClass;
15use crate::fuse::compute_costs;
16use crate::inline::InlineVec;
17use crate::threading::{
18 for_each_inner_block_with_offsets, is_parallel, mapreduce_threaded_fold, SendPtr,
19};
20
21const FOLD_LANES: usize = 16;
23
24const AXIS_SWEEP_MIN_LEN: usize = 16;
27
28const AXIS_SWEEP_BLOCK_BYTES: usize = 64 * 1024;
30
31#[inline(always)]
41fn fold_contiguous<T, Op, M, R, U>(src: &[T], init: U, map_fn: &M, reduce_fn: &R) -> U
42where
43 T: Copy,
44 Op: ElementOp<T>,
45 M: Fn(T) -> U,
46 R: Fn(U, U) -> U,
47 U: Clone,
48{
49 if src.len() < 2 * FOLD_LANES {
50 let mut acc = init;
51 for &value in src {
52 acc = reduce_fn(acc, map_fn(Op::apply(value)));
53 }
54 return acc;
55 }
56 let (head, rest) = src.split_at(FOLD_LANES);
57 let mut lanes: [U; FOLD_LANES] = core::array::from_fn(|lane| map_fn(Op::apply(head[lane])));
58 let mut chunks = rest.chunks_exact(FOLD_LANES);
59 for chunk in chunks.by_ref() {
60 for (lane, &value) in lanes.iter_mut().zip(chunk) {
61 *lane = reduce_fn(lane.clone(), map_fn(Op::apply(value)));
62 }
63 }
64 for (lane, &value) in lanes.iter_mut().zip(chunks.remainder()) {
65 *lane = reduce_fn(lane.clone(), map_fn(Op::apply(value)));
66 }
67 lanes.into_iter().fold(init, reduce_fn)
68}
69
70#[inline(always)]
75unsafe fn fold_run<T, Op, M, R, U>(
76 ptr: *const T,
77 stride: isize,
78 len: usize,
79 init: U,
80 map_fn: &M,
81 reduce_fn: &R,
82) -> U
83where
84 T: Copy,
85 Op: ElementOp<T>,
86 M: Fn(T) -> U,
87 R: Fn(U, U) -> U,
88 U: Clone,
89{
90 if stride == 1 {
91 let run = unsafe { std::slice::from_raw_parts(ptr, len) };
93 return fold_contiguous::<T, Op, M, R, U>(run, init, map_fn, reduce_fn);
94 }
95 let mut acc = init;
96 let mut cursor = ptr;
97 for index in 0..len {
98 acc = reduce_fn(acc, map_fn(Op::apply(unsafe { *cursor })));
100 if index + 1 < len {
101 cursor = cursor.wrapping_offset(stride);
102 }
103 }
104 acc
105}
106
107pub fn reduce<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
140 src: &StridedView<T, Op>,
141 map_fn: M,
142 reduce_fn: R,
143 init: U,
144 exec: Exec<'_>,
145) -> Result<U>
146where
147 M: Fn(T) -> U + Sync,
148 R: Fn(U, U) -> U + Sync,
149 U: Clone + Send + Sync,
150{
151 reduce_impl(src, map_fn, reduce_fn, init, exec)
152}
153
154fn reduce_impl<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
155 src: &StridedView<T, Op>,
156 map_fn: M,
157 reduce_fn: R,
158 init: U,
159 exec: Exec<'_>,
160) -> Result<U>
161where
162 M: Fn(T) -> U + Sync,
163 R: Fn(U, U) -> U + Sync,
164 U: Clone + Send + Sync,
165{
166 let src_ptr = src.ptr();
167 let src_dims = src.dims();
168 let src_strides = src.strides();
169
170 if total_len(src_dims)? == 0 {
174 return Ok(init);
175 }
176
177 let contiguous =
178 sequential_contiguous_layout(KernelClass::Reduce, src_dims, &[src_strides], &exec)?;
179 if contiguous.is_some() {
180 let len = total_len(src_dims)?;
181 let src = unsafe { std::slice::from_raw_parts(src_ptr, len) };
182 return Ok(simd::dispatch_if_large(len, || {
183 fold_contiguous::<T, Op, M, R, U>(src, init, &map_fn, &reduce_fn)
184 }));
185 }
186
187 let total = total_len(src_dims)?;
191 if is_parallel(KernelClass::Reduce, total, &exec)
192 && same_contiguous_layout(src_dims, &[src_strides]).is_some()
193 {
194 let src_slice = unsafe { std::slice::from_raw_parts(src_ptr, total) };
195 let result = crate::threading::parallel_map_reduce(
196 0..total,
197 crate::cost::for_len_tree(exec, KernelClass::Reduce, total),
198 &|range| {
199 simd::dispatch_if_large(range.len(), || {
200 fold_contiguous::<T, Op, M, R, U>(
201 &src_slice[range],
202 init.clone(),
203 &map_fn,
204 &reduce_fn,
205 )
206 })
207 },
208 &|a, b| reduce_fn(a, b),
209 );
210 return Ok(result);
211 }
212
213 let strides_list: [&[isize]; 1] = [src_strides];
214
215 let (fused_dims, ordered_strides, plan) =
216 build_plan_fused(src_dims, &strides_list, None, std::mem::size_of::<T>());
217
218 let total = total_len(&fused_dims)?;
219 if is_parallel(KernelClass::Reduce, total, &exec) {
220 let src_send = SendPtr(src_ptr as *mut T);
221 let costs = compute_costs(&ordered_strides);
222 let initial_offsets: crate::inline::OperandVec<isize> =
223 ordered_strides.iter().map(|_| 0isize).collect();
224 let map_fn_ref = &map_fn;
225 let reduce_fn_ref = &reduce_fn;
226 let init_ref = &init;
227 let result = mapreduce_threaded_fold(
230 &fused_dims,
231 &plan.block,
232 &ordered_strides,
233 &initial_offsets,
234 &costs,
235 KernelClass::Reduce,
236 exec,
237 &|dims, blocks, strides_list, offsets| {
238 let mut acc = init_ref.clone();
239 for_each_inner_block_with_offsets(
240 dims,
241 blocks,
242 strides_list,
243 offsets,
244 |offsets, len, strides| {
245 let mut ptr = unsafe { src_send.as_const().offset(offsets[0]) };
246 let stride = strides[0];
247 for _ in 0..len {
248 let val = Op::apply(unsafe { *ptr });
249 let mapped = map_fn_ref(val);
250 acc = reduce_fn_ref(acc.clone(), mapped);
251 unsafe {
252 ptr = ptr.offset(stride);
253 }
254 }
255 Ok(())
256 },
257 )?;
258 Ok(acc)
259 },
260 &|a, b| reduce_fn_ref(a, b),
261 )?;
262 return Ok(result);
263 }
264
265 let mut acc = init;
266 let initial_offsets: crate::inline::OperandVec<isize> =
267 ordered_strides.iter().map(|_| 0isize).collect();
268 for_each_inner_block_preordered(
269 &fused_dims,
270 &plan.block,
271 &ordered_strides,
272 &initial_offsets,
273 |offsets, len, strides| {
274 let mut ptr = unsafe { src_ptr.offset(offsets[0]) };
275 let stride = strides[0];
276 for _ in 0..len {
277 let val = Op::apply(unsafe { *ptr });
278 let mapped = map_fn(val);
279 acc = reduce_fn(acc.clone(), mapped);
280 unsafe {
281 ptr = ptr.offset(stride);
282 }
283 }
284 Ok(())
285 },
286 )?;
287
288 Ok(acc)
289}
290
291#[inline(always)]
307fn fold_contiguous2<A, B, OpA, OpB, M, R, U>(
308 a: &[A],
309 b: &[B],
310 init: U,
311 map_fn: &M,
312 reduce_fn: &R,
313) -> U
314where
315 A: Copy,
316 B: Copy,
317 OpA: ElementOp<A>,
318 OpB: ElementOp<B>,
319 M: Fn(A, B) -> U,
320 R: Fn(U, U) -> U,
321 U: Clone,
322{
323 debug_assert_eq!(a.len(), b.len());
324 if a.len() < 2 * FOLD_LANES {
325 let mut acc = init;
326 for (&value_a, &value_b) in a.iter().zip(b) {
327 acc = reduce_fn(acc, map_fn(OpA::apply(value_a), OpB::apply(value_b)));
328 }
329 return acc;
330 }
331 let (head_a, rest_a) = a.split_at(FOLD_LANES);
332 let (head_b, rest_b) = b.split_at(FOLD_LANES);
333 let mut lanes: [U; FOLD_LANES] =
334 core::array::from_fn(|lane| map_fn(OpA::apply(head_a[lane]), OpB::apply(head_b[lane])));
335 let mut chunks_a = rest_a.chunks_exact(FOLD_LANES);
336 let mut chunks_b = rest_b.chunks_exact(FOLD_LANES);
337 for (chunk_a, chunk_b) in chunks_a.by_ref().zip(chunks_b.by_ref()) {
338 for ((lane, &value_a), &value_b) in lanes.iter_mut().zip(chunk_a).zip(chunk_b) {
339 *lane = reduce_fn(
340 lane.clone(),
341 map_fn(OpA::apply(value_a), OpB::apply(value_b)),
342 );
343 }
344 }
345 for ((lane, &value_a), &value_b) in lanes
346 .iter_mut()
347 .zip(chunks_a.remainder())
348 .zip(chunks_b.remainder())
349 {
350 *lane = reduce_fn(
351 lane.clone(),
352 map_fn(OpA::apply(value_a), OpB::apply(value_b)),
353 );
354 }
355 lanes.into_iter().fold(init, reduce_fn)
356}
357
358#[inline(always)]
365#[allow(clippy::too_many_arguments)]
366unsafe fn fold_run2<A, B, OpA, OpB, M, R, U>(
367 a_ptr: *const A,
368 a_stride: isize,
369 b_ptr: *const B,
370 b_stride: isize,
371 len: usize,
372 init: &U,
373 map_fn: &M,
374 reduce_fn: &R,
375) -> U
376where
377 A: Copy,
378 B: Copy,
379 OpA: ElementOp<A>,
380 OpB: ElementOp<B>,
381 M: Fn(A, B) -> U,
382 R: Fn(U, U) -> U,
383 U: Clone,
384{
385 if a_stride == 1 && b_stride == 1 {
386 let a = unsafe { std::slice::from_raw_parts(a_ptr, len) };
389 let b = unsafe { std::slice::from_raw_parts(b_ptr, len) };
390 return fold_contiguous2::<A, B, OpA, OpB, M, R, U>(a, b, init.clone(), map_fn, reduce_fn);
391 }
392 let mut acc = init.clone();
393 let mut cursor_a = a_ptr;
394 let mut cursor_b = b_ptr;
395 for index in 0..len {
396 let mapped = map_fn(
398 OpA::apply(unsafe { *cursor_a }),
399 OpB::apply(unsafe { *cursor_b }),
400 );
401 acc = reduce_fn(acc, mapped);
402 if index + 1 < len {
403 cursor_a = cursor_a.wrapping_offset(a_stride);
404 cursor_b = cursor_b.wrapping_offset(b_stride);
405 }
406 }
407 acc
408}
409
410pub fn zip_reduce<A, B, OpA, OpB, M, R, U>(
472 a: &StridedView<A, OpA>,
473 b: &StridedView<B, OpB>,
474 map_fn: M,
475 reduce_fn: R,
476 init: U,
477 exec: Exec<'_>,
478) -> Result<U>
479where
480 A: Copy + Send + Sync,
481 B: Copy + Send + Sync,
482 OpA: ElementOp<A>,
483 OpB: ElementOp<B>,
484 M: Fn(A, B) -> U + Sync,
485 R: Fn(U, U) -> U + Sync,
486 U: Clone + Send + Sync,
487{
488 ensure_same_shape(a.dims(), b.dims())?;
489
490 let a_ptr = a.ptr();
491 let b_ptr = b.ptr();
492 let dims = a.dims();
493 let a_strides = a.strides();
494 let b_strides = b.strides();
495
496 let total = total_len(dims)?;
500 if total == 0 {
501 return Ok(init);
502 }
503
504 if sequential_contiguous_layout(KernelClass::Reduce, dims, &[a_strides, b_strides], &exec)?
505 .is_some()
506 {
507 return Ok(simd::dispatch_if_large(total, || {
508 let sa = unsafe { std::slice::from_raw_parts(a_ptr, total) };
510 let sb = unsafe { std::slice::from_raw_parts(b_ptr, total) };
511 fold_contiguous2::<A, B, OpA, OpB, M, R, U>(sa, sb, init, &map_fn, &reduce_fn)
512 }));
513 }
514
515 if is_parallel(KernelClass::Reduce, total, &exec)
518 && same_contiguous_layout(dims, &[a_strides, b_strides]).is_some()
519 {
520 let sa = unsafe { std::slice::from_raw_parts(a_ptr, total) };
522 let sb = unsafe { std::slice::from_raw_parts(b_ptr, total) };
523 let result = crate::threading::parallel_map_reduce(
524 0..total,
525 crate::cost::for_len_tree(exec, KernelClass::Reduce, total),
526 &|range| {
527 let len = range.len();
528 simd::dispatch_if_large(len, || {
529 fold_contiguous2::<A, B, OpA, OpB, M, R, U>(
530 &sa[range.clone()],
531 &sb[range],
532 init.clone(),
533 &map_fn,
534 &reduce_fn,
535 )
536 })
537 },
538 &|left, right| reduce_fn(left, right),
539 );
540 return Ok(result);
541 }
542
543 let strides_list: [&[isize]; 2] = [a_strides, b_strides];
544 let elem_size = std::mem::size_of::<A>().max(std::mem::size_of::<B>());
545 let (fused_dims, ordered_strides, plan) =
546 build_plan_fused(dims, &strides_list, None, elem_size);
547
548 let total = total_len(&fused_dims)?;
549 if is_parallel(KernelClass::Reduce, total, &exec) {
550 let a_send = SendPtr(a_ptr as *mut A);
551 let b_send = SendPtr(b_ptr as *mut B);
552 let costs = compute_costs(&ordered_strides);
553 let initial_offsets: crate::inline::OperandVec<isize> =
554 ordered_strides.iter().map(|_| 0isize).collect();
555 let map_fn_ref = &map_fn;
556 let reduce_fn_ref = &reduce_fn;
557 let init_ref = &init;
558 let result = mapreduce_threaded_fold(
561 &fused_dims,
562 &plan.block,
563 &ordered_strides,
564 &initial_offsets,
565 &costs,
566 KernelClass::Reduce,
567 exec,
568 &|dims, blocks, strides_list, offsets| {
569 let mut acc = init_ref.clone();
570 for_each_inner_block_with_offsets(
571 dims,
572 blocks,
573 strides_list,
574 offsets,
575 |offsets, len, strides| {
576 acc = unsafe {
579 fold_run2::<A, B, OpA, OpB, M, R, U>(
580 a_send.as_const().offset(offsets[0]),
581 strides[0],
582 b_send.as_const().offset(offsets[1]),
583 strides[1],
584 len,
585 &acc,
586 map_fn_ref,
587 reduce_fn_ref,
588 )
589 };
590 Ok(())
591 },
592 )?;
593 Ok(acc)
594 },
595 &|left, right| reduce_fn_ref(left, right),
596 )?;
597 return Ok(result);
598 }
599
600 let mut acc = init;
601 let initial_offsets: crate::inline::OperandVec<isize> =
602 ordered_strides.iter().map(|_| 0isize).collect();
603 for_each_inner_block_preordered(
604 &fused_dims,
605 &plan.block,
606 &ordered_strides,
607 &initial_offsets,
608 |offsets, len, strides| {
609 acc = unsafe {
612 fold_run2::<A, B, OpA, OpB, M, R, U>(
613 a_ptr.offset(offsets[0]),
614 strides[0],
615 b_ptr.offset(offsets[1]),
616 strides[1],
617 len,
618 &acc,
619 &map_fn,
620 &reduce_fn,
621 )
622 };
623 Ok(())
624 },
625 )?;
626
627 Ok(acc)
628}
629
630pub fn reduce_axis<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
645 src: &StridedView<T, Op>,
646 axis: usize,
647 map_fn: M,
648 reduce_fn: R,
649 init: U,
650 exec: Exec<'_>,
651) -> Result<StridedArray<U>>
652where
653 M: Fn(T) -> U + Sync,
654 R: Fn(U, U) -> U + Sync,
655 U: Clone + Send + Sync,
656{
657 let rank = src.ndim();
658 if axis >= rank {
659 return Err(StridedError::InvalidAxis { axis, rank });
660 }
661 total_len(src.dims())?;
663 let out_dims: InlineVec<usize> = src
664 .dims()
665 .iter()
666 .enumerate()
667 .filter(|(i, _)| *i != axis)
668 .map(|(_, &d)| d)
669 .collect();
670 let (out_dims, out_strides) = if out_dims.is_empty() {
673 let mut dims = InlineVec::new();
674 dims.push(1usize);
675 let strides = col_major_strides(&dims);
676 (dims, strides)
677 } else {
678 let strides = col_major_strides(&out_dims);
679 (out_dims, strides)
680 };
681 let total_out = total_len(&out_dims)?;
682 let mut out =
683 StridedArray::<U>::from_parts(vec![init.clone(); total_out], &out_dims, &out_strides, 0)?;
684 {
685 let mut dest = out.view_mut();
686 reduce_axis_into_impl(&mut dest, src, axis, map_fn, reduce_fn, init, exec)?;
688 }
689 Ok(out)
690}
691
692pub fn reduce_axis_into<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
721 dest: &mut StridedViewMut<U>,
722 src: &StridedView<T, Op>,
723 axis: usize,
724 map_fn: M,
725 reduce_fn: R,
726 init: U,
727 exec: Exec<'_>,
728) -> Result<()>
729where
730 M: Fn(T) -> U + Sync,
731 R: Fn(U, U) -> U + Sync,
732 U: Clone + Send + Sync,
733{
734 let rank = src.ndim();
735 if axis >= rank {
736 return Err(StridedError::InvalidAxis { axis, rank });
737 }
738 let src_dims = src.dims();
739 total_len(src_dims)?;
742 let kept_len = rank - 1;
743 let dest_len = total_len(dest.dims())?;
744 if kept_len == 0 {
745 if dest_len != 1 {
746 return Err(StridedError::ShapeMismatch(vec![dest_len], vec![1]));
747 }
748 } else {
749 if dest.ndim() != kept_len {
750 return Err(StridedError::RankMismatch(dest.ndim(), kept_len));
751 }
752 let dest_dims = dest.dims();
753 let mut d = 0usize;
754 for (i, &src_dim) in src_dims.iter().enumerate() {
755 if i == axis {
756 continue;
757 }
758 if src_dim != dest_dims[d] {
759 let expected: Vec<usize> = src_dims
760 .iter()
761 .enumerate()
762 .filter(|(i, _)| *i != axis)
763 .map(|(_, &d)| d)
764 .collect();
765 return Err(StridedError::ShapeMismatch(dest_dims.to_vec(), expected));
766 }
767 d += 1;
768 }
769 if !crate::layout_check::is_injective_layout(dest.dims(), dest.strides()) {
770 return Err(StridedError::NonInjectiveOutputLayout);
771 }
772 }
773 reduce_axis_into_impl(dest, src, axis, map_fn, reduce_fn, init, exec)
774}
775
776fn reduce_axis_into_impl<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
783 dest: &mut StridedViewMut<U>,
784 src: &StridedView<T, Op>,
785 axis: usize,
786 map_fn: M,
787 reduce_fn: R,
788 init: U,
789 exec: Exec<'_>,
790) -> Result<()>
791where
792 M: Fn(T) -> U + Sync,
793 R: Fn(U, U) -> U + Sync,
794 U: Clone + Send + Sync,
795{
796 let rank = src.ndim();
797 let src_dims = src.dims();
798 let src_strides = src.strides();
799 let src_ptr = src.ptr();
800 let dest_len = total_len(dest.dims())?;
801 let axis_len = src_dims[axis];
802 let axis_stride = src_strides[axis];
803
804 if axis_len == 0 {
805 fill_outputs_with(dest, &init);
810 return Ok(());
811 }
812
813 if rank == 1 {
814 let acc = unsafe {
817 fold_run::<T, Op, M, R, U>(src_ptr, axis_stride, axis_len, init, &map_fn, &reduce_fn)
818 };
819 unsafe { *dest.as_mut_ptr() = acc };
820 return Ok(());
821 }
822 if dest_len == 0 {
823 return Ok(());
824 }
825
826 let dest_stride_view = dest.strides();
831 let mut kept: InlineVec<(usize, isize)> = InlineVec::new();
832 let mut dest_linear = true;
833 let mut prefix = 1usize;
834 for (i, (&dim, &src_stride)) in src_dims.iter().zip(src_strides).enumerate() {
835 if i == axis {
836 continue;
837 }
838 let d = kept.len();
839 kept.push((dim, src_stride));
840 if dest_linear && dest_stride_view[d] != prefix as isize {
841 dest_linear = false;
842 }
843 prefix = prefix.saturating_mul(dim);
844 }
845 let dest_strides: InlineVec<isize> = if dest_linear {
846 InlineVec::new()
847 } else {
848 dest_stride_view.iter().copied().collect()
849 };
850 let out_ptr = dest.as_mut_ptr();
851 let parts = AxisParts {
852 kept: &kept,
853 dest_strides: &dest_strides,
854 axis_len,
855 axis_stride,
856 map_fn: &map_fn,
857 reduce_fn: &reduce_fn,
858 init: &init,
859 };
860
861 let work = dest_len.saturating_mul(axis_len);
862 let exec = crate::cost::for_len(exec, KernelClass::Reduce, work)
863 .limit(NonZeroUsize::new(dest_len).unwrap_or(NonZeroUsize::MIN));
864 if !exec.is_serial() {
865 let src_send = SendPtr(src_ptr as *mut T);
866 let out_send = SendPtr(out_ptr);
867 let parts = &parts;
868 if dest_linear {
869 crate::threading::parallel_for_each(0..dest_len, exec, &|range| {
870 unsafe {
874 reduce_axis_range_linear::<T, Op, M, R, U>(
875 src_send.as_const(),
876 out_send.as_ptr(),
877 range,
878 parts,
879 )
880 }
881 });
882 } else {
883 crate::threading::parallel_for_each(0..dest_len, exec, &|range| {
884 unsafe {
886 reduce_axis_range_strided::<T, Op, M, R, U>(
887 src_send.as_const(),
888 out_send.as_ptr(),
889 range,
890 parts,
891 )
892 }
893 });
894 }
895 return Ok(());
896 }
897
898 if dest_linear {
900 unsafe {
901 reduce_axis_range_linear::<T, Op, M, R, U>(src_ptr, out_ptr, 0..dest_len, &parts)
902 };
903 } else {
904 unsafe {
905 reduce_axis_range_strided::<T, Op, M, R, U>(src_ptr, out_ptr, 0..dest_len, &parts)
906 };
907 }
908 Ok(())
909}
910
911fn fill_outputs_with<U: Clone>(dest: &mut StridedViewMut<U>, value: &U) {
918 let dims = dest.dims();
919 if dims.iter().any(|&dim| dim == 0) {
920 return;
921 }
922 let strides = dest.strides();
923 let rank = dims.len();
924 let total: usize = dims.iter().product();
925 let base = dest.as_mut_ptr();
926 if rank == 0 {
927 unsafe { *base = value.clone() };
929 return;
930 }
931 let mut coords_inline = [0usize; strided_view::INLINE_RANK];
932 let mut coords_heap: Vec<usize> = Vec::new();
933 let coords: &mut [usize] = if rank <= strided_view::INLINE_RANK {
934 &mut coords_inline[..rank]
935 } else {
936 coords_heap.resize(rank, 0);
937 &mut coords_heap[..]
938 };
939 let mut offset = 0isize;
940 for _ in 0..total {
941 unsafe { *base.offset(offset) = value.clone() };
945 for axis in 0..rank {
946 coords[axis] += 1;
947 offset += strides[axis];
948 if coords[axis] < dims[axis] {
949 break;
950 }
951 coords[axis] = 0;
952 offset -= dims[axis] as isize * strides[axis];
953 }
954 }
955}
956
957struct AxisParts<'a, M, R, U> {
960 kept: &'a [(usize, isize)],
962 dest_strides: &'a [isize],
964 axis_len: usize,
965 axis_stride: isize,
966 map_fn: &'a M,
967 reduce_fn: &'a R,
968 init: &'a U,
969}
970
971#[inline(never)]
984unsafe fn reduce_axis_range_linear<T, Op, M, R, U>(
985 src: *const T,
986 out: *mut U,
987 range: Range<usize>,
988 parts: &AxisParts<'_, M, R, U>,
989) where
990 T: Copy,
991 Op: ElementOp<T>,
992 M: Fn(T) -> U,
993 R: Fn(U, U) -> U,
994 U: Clone,
995{
996 let kept = parts.kept;
997 let (lead_extent, lead_stride) = kept[0];
998 let sweep = lead_stride == 1 && lead_extent >= AXIS_SWEEP_MIN_LEN && parts.axis_len > 1;
999 let block = (AXIS_SWEEP_BLOCK_BYTES / std::mem::size_of::<U>().max(1)).max(AXIS_SWEEP_MIN_LEN);
1000
1001 let coords_len = kept.len();
1006 let mut coords_inline = [0usize; strided_view::INLINE_RANK];
1007 let mut coords_heap: Vec<usize> = Vec::new();
1008 let coords: &mut [usize] = if coords_len <= strided_view::INLINE_RANK {
1009 &mut coords_inline[..coords_len]
1010 } else {
1011 coords_heap.resize(coords_len, 0);
1012 &mut coords_heap[..]
1013 };
1014 let mut rest = range.start;
1015 let mut src_off = 0isize;
1016 for (coord, &(extent, stride)) in coords.iter_mut().zip(kept) {
1017 *coord = rest % extent;
1018 rest /= extent;
1019 src_off += *coord as isize * stride;
1020 }
1021
1022 let mut output = range.start;
1023 while output < range.end {
1024 let len = if sweep {
1025 (lead_extent - coords[0]).min(range.end - output).min(block)
1026 } else {
1027 1
1028 };
1029 if sweep {
1030 unsafe { sweep_block::<T, Op, M, R, U>(src, src_off, out.add(output), len, parts) };
1034 } else {
1035 unsafe {
1037 let acc = fold_run::<T, Op, M, R, U>(
1038 src.offset(src_off),
1039 parts.axis_stride,
1040 parts.axis_len,
1041 parts.init.clone(),
1042 parts.map_fn,
1043 parts.reduce_fn,
1044 );
1045 *out.add(output) = acc;
1046 }
1047 }
1048 output += len;
1049 if output >= range.end {
1050 break;
1051 }
1052 coords[0] += len;
1055 src_off += len as isize * lead_stride;
1056 let mut axis = 0;
1057 while coords[axis] == kept[axis].0 {
1058 src_off -= kept[axis].0 as isize * kept[axis].1;
1059 coords[axis] = 0;
1060 axis += 1;
1061 coords[axis] += 1;
1062 src_off += kept[axis].1;
1063 }
1064 }
1065}
1066
1067#[inline(never)]
1077unsafe fn reduce_axis_range_strided<T, Op, M, R, U>(
1078 src: *const T,
1079 out: *mut U,
1080 range: Range<usize>,
1081 parts: &AxisParts<'_, M, R, U>,
1082) where
1083 T: Copy,
1084 Op: ElementOp<T>,
1085 M: Fn(T) -> U,
1086 R: Fn(U, U) -> U,
1087 U: Clone,
1088{
1089 let kept = parts.kept;
1090 let dest_strides = parts.dest_strides;
1091 let (lead_extent, lead_stride) = kept[0];
1092 let lead_dest_stride = dest_strides[0];
1093 let sweep = lead_stride == 1
1094 && lead_dest_stride == 1
1095 && lead_extent >= AXIS_SWEEP_MIN_LEN
1096 && parts.axis_len > 1;
1097 let block = (AXIS_SWEEP_BLOCK_BYTES / std::mem::size_of::<U>().max(1)).max(AXIS_SWEEP_MIN_LEN);
1098
1099 let coords_len = kept.len();
1103 let mut coords_inline = [0usize; strided_view::INLINE_RANK];
1104 let mut coords_heap: Vec<usize> = Vec::new();
1105 let coords: &mut [usize] = if coords_len <= strided_view::INLINE_RANK {
1106 &mut coords_inline[..coords_len]
1107 } else {
1108 coords_heap.resize(coords_len, 0);
1109 &mut coords_heap[..]
1110 };
1111 let mut rest = range.start;
1112 let mut src_off = 0isize;
1113 let mut dest_off = 0isize;
1114 for (coord, (&(extent, stride), &dest_stride)) in
1115 coords.iter_mut().zip(kept.iter().zip(dest_strides))
1116 {
1117 *coord = rest % extent;
1118 rest /= extent;
1119 src_off += *coord as isize * stride;
1120 dest_off += *coord as isize * dest_stride;
1121 }
1122
1123 let mut output = range.start;
1124 while output < range.end {
1125 let len = if sweep {
1126 (lead_extent - coords[0]).min(range.end - output).min(block)
1127 } else {
1128 1
1129 };
1130 if sweep {
1131 unsafe {
1134 sweep_block::<T, Op, M, R, U>(src, src_off, out.offset(dest_off), len, parts)
1135 };
1136 } else {
1137 unsafe {
1139 let acc = fold_run::<T, Op, M, R, U>(
1140 src.offset(src_off),
1141 parts.axis_stride,
1142 parts.axis_len,
1143 parts.init.clone(),
1144 parts.map_fn,
1145 parts.reduce_fn,
1146 );
1147 *out.offset(dest_off) = acc;
1148 }
1149 }
1150 output += len;
1151 if output >= range.end {
1152 break;
1153 }
1154 coords[0] += len;
1155 src_off += len as isize * lead_stride;
1156 dest_off += len as isize * lead_dest_stride;
1157 let mut axis = 0;
1158 while coords[axis] == kept[axis].0 {
1159 src_off -= kept[axis].0 as isize * kept[axis].1;
1160 dest_off -= kept[axis].0 as isize * dest_strides[axis];
1161 coords[axis] = 0;
1162 axis += 1;
1163 coords[axis] += 1;
1164 src_off += kept[axis].1;
1165 dest_off += dest_strides[axis];
1166 }
1167 }
1168}
1169
1170#[inline(always)]
1178unsafe fn sweep_block<T, Op, M, R, U>(
1179 src: *const T,
1180 src_off: isize,
1181 out: *mut U,
1182 len: usize,
1183 parts: &AxisParts<'_, M, R, U>,
1184) where
1185 T: Copy,
1186 Op: ElementOp<T>,
1187 M: Fn(T) -> U,
1188 R: Fn(U, U) -> U,
1189 U: Clone,
1190{
1191 let map_fn = parts.map_fn;
1192 let reduce_fn = parts.reduce_fn;
1193 let out = unsafe { std::slice::from_raw_parts_mut(out, len) };
1195 let column = |k: usize| {
1196 unsafe {
1198 std::slice::from_raw_parts(src.offset(src_off + k as isize * parts.axis_stride), len)
1199 }
1200 };
1201 let first = column(0);
1202 for (slot, &value) in out.iter_mut().zip(first) {
1203 *slot = reduce_fn(parts.init.clone(), map_fn(Op::apply(value)));
1204 }
1205 let mut k = 1;
1206 while k + 4 <= parts.axis_len {
1207 let (c0, c1, c2, c3) = (column(k), column(k + 1), column(k + 2), column(k + 3));
1208 for (index, slot) in out.iter_mut().enumerate() {
1209 let mut acc = reduce_fn(slot.clone(), map_fn(Op::apply(c0[index])));
1210 acc = reduce_fn(acc, map_fn(Op::apply(c1[index])));
1211 acc = reduce_fn(acc, map_fn(Op::apply(c2[index])));
1212 acc = reduce_fn(acc, map_fn(Op::apply(c3[index])));
1213 *slot = acc;
1214 }
1215 k += 4;
1216 }
1217 while k < parts.axis_len {
1218 let values = column(k);
1219 for (slot, &value) in out.iter_mut().zip(values) {
1220 *slot = reduce_fn(slot.clone(), map_fn(Op::apply(value)));
1221 }
1222 k += 1;
1223 }
1224}