use alloc::vec::Vec;
use p3_maybe_rayon::prelude::*;
use p3_util::DisjointMutPtr;
#[derive(Copy, Clone, Debug)]
pub(crate) enum Dispatch {
Serial,
Parallel {
min_len: usize,
},
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct StagedRuns {
run: usize,
log_stride: usize,
depth: usize,
}
impl StagedRuns {
pub(crate) const fn new(run: usize, log_stride: usize, depth: usize) -> Self {
assert!(
depth < usize::BITS as usize && log_stride < usize::BITS as usize - depth,
"staging shifts exceed the word size"
);
Self {
run,
log_stride,
depth,
}
}
#[inline]
pub(crate) const fn block(&self, index: usize) -> usize {
index >> self.log_stride
}
#[inline]
pub(crate) const fn run_index(&self, index: usize, k: usize) -> usize {
let offset = index & ((1 << self.log_stride) - 1);
(self.block(index) << (self.log_stride + self.depth)) + offset + (k << self.log_stride)
}
}
pub(crate) fn for_each_staged_tile<T, P>(
values: &mut [T],
runs: StagedRuns,
dispatch: Dispatch,
process: P,
) where
T: Copy + Send + Sync,
P: Fn(&mut [T], usize) + Send + Sync,
{
let StagedRuns { run, depth, .. } = runs;
let len = values.len();
let rows = 1 << depth;
let tile_len = run << depth;
let tiles = len / tile_len;
let count = len / run;
assert_eq!(tiles * tile_len, len, "tiles do not partition the matrix");
let base = DisjointMutPtr::new(values);
let task = move |tile: &mut Vec<T>, index: usize| {
assert!(
runs.run_index(index, rows - 1) < count,
"staged row walk leaves the matrix"
);
tile.clear();
for k in 0..rows {
let source = unsafe { base.slice(runs.run_index(index, k) * run, run) };
tile.extend_from_slice(source);
}
debug_assert_eq!(tile.len(), tile_len, "the gather left the tile short");
process(tile.as_mut_slice(), runs.block(index));
for (k, source) in tile.chunks_exact(run).enumerate() {
let target = unsafe { base.slice_mut(runs.run_index(index, k) * run, run) };
target.copy_from_slice(source);
}
};
let new_tile = || Vec::with_capacity(tile_len);
match dispatch {
Dispatch::Parallel { min_len } => (0..tiles)
.into_par_iter()
.with_min_len(min_len)
.for_each_init(new_tile, task),
Dispatch::Serial => {
let mut tile = new_tile();
for index in 0..tiles {
task(&mut tile, index);
}
}
}
}
pub(crate) fn for_each_staged_tile_into_cosets<T, Q, P>(
values: &mut [T],
coset_len: usize,
runs: StagedRuns,
dispatch: Dispatch,
prepare: Q,
process: P,
) where
T: Copy + Send + Sync,
Q: Fn(&mut [T]) + Send + Sync,
P: Fn(&mut [T], usize, usize) + Send + Sync,
{
let StagedRuns { run, depth, .. } = runs;
let cosets = values.len() / coset_len;
let rows = 1 << depth;
let tile_len = run << depth;
let tiles = coset_len / tile_len;
let count = coset_len / run;
assert_eq!(
cosets * coset_len,
values.len(),
"cosets do not partition the matrix"
);
assert_eq!(
tiles * tile_len,
coset_len,
"tiles do not partition the coset"
);
let base = DisjointMutPtr::new(values);
let slice_of = move |index: usize, k: usize, coset: usize| {
coset * coset_len + runs.run_index(index, k) * run
};
let task = move |(tile, copy): &mut (Vec<T>, Vec<T>), index: usize| {
assert!(
runs.run_index(index, rows - 1) < count,
"staged row walk leaves the coset"
);
tile.clear();
for k in 0..rows {
let source = unsafe { base.slice(slice_of(index, k, 0), run) };
tile.extend_from_slice(source);
}
debug_assert_eq!(tile.len(), tile_len, "the gather left the tile short");
prepare(tile.as_mut_slice());
let block = runs.block(index);
for coset in (0..cosets).rev() {
let staged = if coset == 0 {
&mut *tile
} else {
copy.clear();
copy.extend_from_slice(&tile[..]);
&mut *copy
};
process(staged.as_mut_slice(), block, coset);
for (k, source) in staged.chunks_exact(run).enumerate() {
let target = unsafe { base.slice_mut(slice_of(index, k, coset), run) };
target.copy_from_slice(source);
}
}
};
let new_tiles = || (Vec::with_capacity(tile_len), Vec::with_capacity(tile_len));
match dispatch {
Dispatch::Parallel { min_len } => (0..tiles)
.into_par_iter()
.with_min_len(min_len)
.for_each_init(new_tiles, task),
Dispatch::Serial => {
let mut buffers = new_tiles();
for index in 0..tiles {
task(&mut buffers, index);
}
}
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use alloc::{format, vec};
use super::{Dispatch, StagedRuns, for_each_staged_tile, for_each_staged_tile_into_cosets};
#[test]
fn the_tiles_partition_the_runs() {
for log_runs in 0..=8usize {
for depth in 0..=log_runs {
for log_stride in 0..=log_runs - depth {
let runs = StagedRuns::new(1, log_stride, depth);
let label =
format!("log_runs={log_runs} log_stride={log_stride} depth={depth}");
let mut seen = vec![false; 1 << log_runs];
for index in 0..1usize << (log_runs - depth) {
for k in 0..1usize << depth {
let run = runs.run_index(index, k);
let hit = seen
.get_mut(run)
.unwrap_or_else(|| panic!("run {run} out of range, {label}"));
assert!(!*hit, "run {run} staged twice, {label}");
*hit = true;
assert_eq!(
run >> (log_stride + depth),
runs.block(index),
"index={index} k={k} {label}"
);
}
}
assert!(seen.iter().all(|&hit| hit), "a run was left out, {label}");
}
}
}
}
#[test]
fn a_pass_writes_back_what_the_callback_left() {
let dispatches = [
Dispatch::Serial,
Dispatch::Parallel { min_len: 1 },
Dispatch::Parallel { min_len: 4 },
];
for dispatch in dispatches {
for run in [1usize, 3] {
for (log_runs, log_stride, depth) in [(6, 2, 3), (6, 0, 6), (6, 6, 0), (5, 1, 2)] {
let len = run << log_runs;
let mut values: Vec<u64> = (0..len as u64).collect();
let shift = len as u64;
let label = format!(
"{dispatch:?} run={run} log_runs={log_runs} log_stride={log_stride} \
depth={depth}"
);
for_each_staged_tile(
&mut values,
StagedRuns::new(run, log_stride, depth),
dispatch,
|tile, block| {
for (k, staged) in tile.chunks_exact_mut(run).enumerate() {
let r = staged[0] as usize / run;
for (j, &value) in staged.iter().enumerate() {
assert_eq!(value, (r * run + j) as u64, "{label}");
}
assert_eq!(r >> (log_stride + depth), block, "{label}");
assert_eq!((r >> log_stride) & ((1 << depth) - 1), k, "{label}");
for value in staged.iter_mut() {
*value += shift;
}
}
},
);
for (i, &value) in values.iter().enumerate() {
assert_eq!(value, i as u64 + shift, "i={i} {label}");
}
}
}
}
}
#[test]
fn every_coset_receives_its_own_result_of_the_leading_one() {
let dispatches = [
Dispatch::Serial,
Dispatch::Parallel { min_len: 1 },
Dispatch::Parallel { min_len: 4 },
];
for dispatch in dispatches {
for cosets in [1usize, 2, 4] {
for run in [1usize, 3] {
for (log_runs, log_stride, depth) in
[(6, 2, 3), (6, 0, 6), (6, 6, 0), (5, 1, 2)]
{
let coset_len = run << log_runs;
let label = format!(
"{dispatch:?} cosets={cosets} run={run} log_runs={log_runs} \
log_stride={log_stride} depth={depth}"
);
let mut values: Vec<u64> = (0..coset_len as u64)
.chain(core::iter::repeat_n(u64::MAX, (cosets - 1) * coset_len))
.collect();
for_each_staged_tile_into_cosets(
&mut values,
coset_len,
StagedRuns::new(run, log_stride, depth),
dispatch,
|tile| tile.iter_mut().for_each(|value| *value *= 2),
|tile, block, coset| {
for (k, staged) in tile.chunks_exact_mut(run).enumerate() {
let r = staged[0] as usize / (2 * run);
for (j, &value) in staged.iter().enumerate() {
assert_eq!(value, 2 * (r * run + j) as u64, "{label}");
}
assert_eq!(r >> (log_stride + depth), block, "{label}");
assert_eq!(
(r >> log_stride) & ((1 << depth) - 1),
k,
"{label}"
);
for value in staged.iter_mut() {
*value += (coset * coset_len) as u64;
}
}
},
);
for (i, &value) in values.iter().enumerate() {
let (coset, offset) = (i / coset_len, i % coset_len);
let want = 2 * offset as u64 + (coset * coset_len) as u64;
assert_eq!(value, want, "i={i} {label}");
}
}
}
}
}
}
#[test]
#[should_panic = "cosets do not partition the matrix"]
fn cosets_that_do_not_partition_the_matrix_are_refused() {
let mut values = vec![0u8; 12];
for_each_staged_tile_into_cosets(
&mut values,
8,
StagedRuns::new(1, 1, 2),
Dispatch::Serial,
|_| {},
|_, _, _| {},
);
}
#[test]
#[should_panic = "tiles do not partition the matrix"]
fn a_tile_longer_than_the_matrix_is_refused() {
let mut values = vec![0u8; 3];
for_each_staged_tile(
&mut values,
StagedRuns::new(1, 0, 2),
Dispatch::Serial,
|_, _| {},
);
}
#[test]
#[should_panic = "staged row walk leaves the matrix"]
fn a_walk_past_the_matrix_is_refused() {
let mut values = vec![0u8; 6];
for_each_staged_tile(
&mut values,
StagedRuns::new(1, 1, 1),
Dispatch::Serial,
|_, _| {},
);
}
}