use p3_maybe_rayon::prelude::current_num_threads;
use p3_util::{log2_ceil_usize, log2_floor_usize};
const TILE_BYTES: usize = 32 * 1024;
const STAGING_BYTES: usize = 64 * 1024;
pub(super) const DEEP_TILE_BYTES: usize = 256 * 1024;
pub(super) const SHARED_CACHE_BYTES: usize = 128 * 1024 * 1024;
const _: () = assert!(SHARED_CACHE_BYTES / DEEP_TILE_BYTES >= 256);
const STAGED_LINE_BYTES: usize = 64;
pub(super) const STAGED_RUN_BYTES: usize = 1024;
const STAGED_GATHER_DOUBLINGS: usize = 3;
pub(super) const STAGED_WORKERS: usize = 4;
#[derive(Copy, Clone, Debug)]
pub(super) struct Plan {
pub(super) width: usize,
pub(super) log_n: usize,
pub(super) local: usize,
pub(super) log_block: usize,
pub(super) depth: usize,
}
impl Plan {
pub(super) const fn leftover_stages(above: usize, depth: usize) -> usize {
if depth < 2 {
above
} else if above % depth == 1 {
1
} else {
0
}
}
pub(super) fn groups(above: usize, depth: usize) -> impl Iterator<Item = usize> {
let leftover = Self::leftover_stages(above, depth);
let mut remaining = above;
core::iter::from_fn(move || {
(remaining > leftover).then(|| {
let take = depth.min(remaining - leftover);
remaining -= take;
take
})
})
}
fn traversals(above: usize, depth: usize) -> usize {
Self::groups(above, depth).count() + Self::leftover_stages(above, depth)
}
pub(super) fn budgets(row: usize, log_n: usize) -> (usize, usize) {
if log_n > log2_floor_usize((SHARED_CACHE_BYTES / row).max(1)) {
(DEEP_TILE_BYTES, DEEP_TILE_BYTES)
} else {
(TILE_BYTES, STAGING_BYTES)
}
}
pub(super) fn new(width: usize, log_n: usize) -> Self {
Self::for_workers(width, log_n, current_num_threads())
}
pub(super) fn for_workers(width: usize, log_n: usize, workers: usize) -> Self {
let row = size_of::<u128>() * width;
let (tile, staging) = Self::budgets(row, log_n);
let local = log2_floor_usize((tile / row).max(1)).min(log_n);
let above = log_n - local;
let depth_at = |log_block: usize| log2_floor_usize((staging / (row << log_block)).max(1));
let floor = log2_ceil_usize(STAGED_LINE_BYTES.div_ceil(row));
let target = log2_ceil_usize(STAGED_RUN_BYTES.div_ceil(row)).max(floor);
let budget = Self::traversals(above, depth_at(floor));
let gathers = Self::groups(above, depth_at(floor)).count();
let log_block = (floor..=target)
.rev()
.find(|&log_block| {
let depth = depth_at(log_block);
let paid = log_block >= floor + STAGED_GATHER_DOUBLINGS;
Self::traversals(above, depth) <= budget
&& (paid || Self::groups(above, depth).count() <= gathers)
})
.unwrap_or(floor);
let depth = if row >= STAGED_LINE_BYTES || workers >= STAGED_WORKERS {
depth_at(log_block)
} else {
0
};
Self {
width,
log_n,
local,
log_block,
depth,
}
}
pub(super) const fn leftover(&self) -> usize {
Self::leftover_stages(self.log_n - self.local, self.depth)
}
pub(super) fn group_sizes(&self) -> impl Iterator<Item = usize> {
Self::groups(self.log_n - self.local, self.depth)
}
}