add efficient prefix-sums and reductions #22
|
@ -29,4 +29,5 @@ pub use misc::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod job_server;
|
pub mod job_server;
|
||||||
|
pub mod prefix_sum;
|
||||||
pub mod ready_valid;
|
pub mod ready_valid;
|
||||||
|
|
839
crates/fayalite/src/util/prefix_sum.rs
Normal file
839
crates/fayalite/src/util/prefix_sum.rs
Normal file
|
@ -0,0 +1,839 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
// code derived from:
|
||||||
|
// https://web.archive.org/web/20250303054010/https://git.libre-soc.org/?p=nmutil.git;a=blob;f=src/nmutil/prefix_sum.py;hb=effeb28e5848392adddcdad1f6e7a098f2a44c9c
|
||||||
|
|
||||||
|
use crate::intern::{Intern, Interned, Memoize};
|
||||||
|
use std::{borrow::Cow, num::NonZeroUsize};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub struct PrefixSumOp {
|
||||||
|
pub lhs_index: usize,
|
||||||
|
pub rhs_and_dest_index: NonZeroUsize,
|
||||||
|
pub row: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct DiagramConfig {
|
||||||
|
pub space: Cow<'static, str>,
|
||||||
|
pub vertical_bar: Cow<'static, str>,
|
||||||
|
pub plus: Cow<'static, str>,
|
||||||
|
pub slant: Cow<'static, str>,
|
||||||
|
pub connect: Cow<'static, str>,
|
||||||
|
pub no_connect: Cow<'static, str>,
|
||||||
|
pub padding: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiagramConfig {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
space: Cow::Borrowed(" "),
|
||||||
|
vertical_bar: Cow::Borrowed("|"),
|
||||||
|
plus: Cow::Borrowed("\u{2295}"), // ⊕
|
||||||
|
slant: Cow::Borrowed(r"\"),
|
||||||
|
connect: Cow::Borrowed("\u{25CF}"), // ●
|
||||||
|
no_connect: Cow::Borrowed("X"),
|
||||||
|
padding: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn draw(self, ops: impl IntoIterator<Item = PrefixSumOp>, item_count: usize) -> String {
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
struct DiagramCell {
|
||||||
|
slant: bool,
|
||||||
|
plus: bool,
|
||||||
|
tee: bool,
|
||||||
|
}
|
||||||
|
let mut ops_by_row: Vec<Vec<PrefixSumOp>> = Vec::new();
|
||||||
|
let mut last_row = 0;
|
||||||
|
ops.into_iter().for_each(|op| {
|
||||||
|
assert!(
|
||||||
|
op.lhs_index < op.rhs_and_dest_index.get(),
|
||||||
|
"invalid PrefixSumOp! lhs_index must be less \
|
||||||
|
than rhs_and_dest_index: {op:?}",
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
op.row >= last_row,
|
||||||
|
"invalid PrefixSumOp! row must \
|
||||||
|
not decrease (row last was: {last_row}): {op:?}",
|
||||||
|
);
|
||||||
|
let ops = if op.row > last_row || ops_by_row.is_empty() {
|
||||||
|
ops_by_row.push(vec![]);
|
||||||
|
ops_by_row.last_mut().expect("just pushed")
|
||||||
|
} else {
|
||||||
|
ops_by_row
|
||||||
|
.last_mut()
|
||||||
|
.expect("just checked if ops_by_row is empty")
|
||||||
|
};
|
||||||
|
if let Some(last) = ops.last() {
|
||||||
|
assert!(
|
||||||
|
op.rhs_and_dest_index < last.rhs_and_dest_index,
|
||||||
|
"invalid PrefixSumOp! rhs_and_dest_index must strictly \
|
||||||
|
decrease in a row:\nthis op: {op:?}\nlast op: {last:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ops.push(op);
|
||||||
|
last_row = op.row;
|
||||||
|
});
|
||||||
|
let blank_row = || {
|
||||||
|
vec![
|
||||||
|
DiagramCell {
|
||||||
|
slant: false,
|
||||||
|
plus: false,
|
||||||
|
tee: false
|
||||||
|
};
|
||||||
|
item_count
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let mut cells = vec![blank_row()];
|
||||||
|
for ops in ops_by_row {
|
||||||
|
let max_distance = ops
|
||||||
|
.iter()
|
||||||
|
.map(
|
||||||
|
|&PrefixSumOp {
|
||||||
|
lhs_index,
|
||||||
|
rhs_and_dest_index,
|
||||||
|
..
|
||||||
|
}| { rhs_and_dest_index.get() - lhs_index },
|
||||||
|
)
|
||||||
|
.max()
|
||||||
|
.expect("ops is known to be non-empty");
|
||||||
|
cells.extend((0..max_distance).map(|_| blank_row()));
|
||||||
|
for op in ops {
|
||||||
|
let mut y = cells.len() - 1;
|
||||||
|
assert!(
|
||||||
|
op.rhs_and_dest_index.get() < item_count,
|
||||||
|
"invalid PrefixSumOp! rhs_and_dest_index must be \
|
||||||
|
less than item_count ({item_count}): {op:?}",
|
||||||
|
);
|
||||||
|
let mut x = op.rhs_and_dest_index.get();
|
||||||
|
cells[y][x].plus = true;
|
||||||
|
x -= 1;
|
||||||
|
y -= 1;
|
||||||
|
while op.lhs_index < x {
|
||||||
|
cells[y][x].slant = true;
|
||||||
|
x -= 1;
|
||||||
|
y -= 1;
|
||||||
|
}
|
||||||
|
cells[y][x].tee = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut retval = String::new();
|
||||||
|
let mut row_text = vec![String::new(); 2 * self.padding + 1];
|
||||||
|
for cells_row in cells {
|
||||||
|
for cell in cells_row {
|
||||||
|
// top padding
|
||||||
|
for y in 0..self.padding {
|
||||||
|
// top left padding
|
||||||
|
for x in 0..self.padding {
|
||||||
|
row_text[y] += if x == y && (cell.plus || cell.slant) {
|
||||||
|
&self.slant
|
||||||
|
} else {
|
||||||
|
&self.space
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// top vertical bar
|
||||||
|
row_text[y] += &self.vertical_bar;
|
||||||
|
// top right padding
|
||||||
|
for _ in 0..self.padding {
|
||||||
|
row_text[y] += &self.space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// center left padding
|
||||||
|
for _ in 0..self.padding {
|
||||||
|
row_text[self.padding] += &self.space;
|
||||||
|
}
|
||||||
|
// center
|
||||||
|
row_text[self.padding] += if cell.plus {
|
||||||
|
&self.plus
|
||||||
|
} else if cell.tee {
|
||||||
|
&self.connect
|
||||||
|
} else if cell.slant {
|
||||||
|
&self.no_connect
|
||||||
|
} else {
|
||||||
|
&self.vertical_bar
|
||||||
|
};
|
||||||
|
// center right padding
|
||||||
|
for _ in 0..self.padding {
|
||||||
|
row_text[self.padding] += &self.space;
|
||||||
|
}
|
||||||
|
let bottom_padding_start = self.padding + 1;
|
||||||
|
let bottom_padding_last = self.padding * 2;
|
||||||
|
// bottom padding
|
||||||
|
for y in bottom_padding_start..=bottom_padding_last {
|
||||||
|
// bottom left padding
|
||||||
|
for _ in 0..self.padding {
|
||||||
|
row_text[y] += &self.space;
|
||||||
|
}
|
||||||
|
// bottom vertical bar
|
||||||
|
row_text[y] += &self.vertical_bar;
|
||||||
|
// bottom right padding
|
||||||
|
for x in bottom_padding_start..=bottom_padding_last {
|
||||||
|
row_text[y] += if x == y && (cell.tee || cell.slant) {
|
||||||
|
&self.slant
|
||||||
|
} else {
|
||||||
|
&self.space
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for line in &mut row_text {
|
||||||
|
retval += line.trim_end();
|
||||||
|
retval += "\n";
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiagramConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrefixSumOp {
|
||||||
|
pub fn diagram(ops: impl IntoIterator<Item = Self>, item_count: usize) -> String {
|
||||||
|
Self::diagram_with_config(ops, item_count, DiagramConfig::new())
|
||||||
|
}
|
||||||
|
pub fn diagram_with_config(
|
||||||
|
ops: impl IntoIterator<Item = Self>,
|
||||||
|
item_count: usize,
|
||||||
|
config: DiagramConfig,
|
||||||
|
) -> String {
|
||||||
|
config.draw(ops, item_count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub enum PrefixSumAlgorithm {
|
||||||
|
/// Uses the algorithm from:
|
||||||
|
/// https://en.wikipedia.org/wiki/Prefix_sum#Algorithm_1:_Shorter_span,_more_parallel
|
||||||
|
LowLatency,
|
||||||
|
/// Uses the algorithm from:
|
||||||
|
/// https://en.wikipedia.org/wiki/Prefix_sum#Algorithm_2:_Work-efficient
|
||||||
|
WorkEfficient,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrefixSumAlgorithm {
|
||||||
|
fn ops_impl(self, item_count: usize) -> Vec<PrefixSumOp> {
|
||||||
|
let mut retval = Vec::new();
|
||||||
|
let mut distance = 1;
|
||||||
|
let mut row = 0;
|
||||||
|
while distance < item_count {
|
||||||
|
let double_distance = distance
|
||||||
|
.checked_mul(2)
|
||||||
|
.expect("prefix-sum item_count is too big");
|
||||||
|
let (start, step) = match self {
|
||||||
|
Self::LowLatency => (distance, 1),
|
||||||
|
Self::WorkEfficient => (double_distance - 1, double_distance),
|
||||||
|
};
|
||||||
|
for rhs_and_dest_index in (start..item_count).step_by(step).rev() {
|
||||||
|
let Some(rhs_and_dest_index) = NonZeroUsize::new(rhs_and_dest_index) else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let lhs_index = rhs_and_dest_index.get() - distance;
|
||||||
|
retval.push(PrefixSumOp {
|
||||||
|
lhs_index,
|
||||||
|
rhs_and_dest_index,
|
||||||
|
row,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
distance = double_distance;
|
||||||
|
row += 1;
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Self::LowLatency => {}
|
||||||
|
Self::WorkEfficient => {
|
||||||
|
distance /= 2;
|
||||||
|
while distance >= 1 {
|
||||||
|
let start = distance
|
||||||
|
.checked_mul(3)
|
||||||
|
.expect("prefix-sum item_count is too big")
|
||||||
|
- 1;
|
||||||
|
for rhs_and_dest_index in (start..item_count).step_by(distance * 2).rev() {
|
||||||
|
let Some(rhs_and_dest_index) = NonZeroUsize::new(rhs_and_dest_index) else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let lhs_index = rhs_and_dest_index.get() - distance;
|
||||||
|
retval.push(PrefixSumOp {
|
||||||
|
lhs_index,
|
||||||
|
rhs_and_dest_index,
|
||||||
|
row,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
row += 1;
|
||||||
|
distance /= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
pub fn ops(self, item_count: usize) -> Interned<[PrefixSumOp]> {
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
struct MyMemoize(PrefixSumAlgorithm);
|
||||||
|
impl Memoize for MyMemoize {
|
||||||
|
type Input = usize;
|
||||||
|
type InputOwned = usize;
|
||||||
|
type Output = Interned<[PrefixSumOp]>;
|
||||||
|
|
||||||
|
fn inner(self, item_count: &Self::Input) -> Self::Output {
|
||||||
|
Intern::intern_owned(self.0.ops_impl(*item_count))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MyMemoize(self).get_owned(item_count)
|
||||||
|
}
|
||||||
|
pub fn run<T>(self, items: impl IntoIterator<Item = T>, f: impl FnMut(&T, &T) -> T) -> Vec<T> {
|
||||||
|
let mut items = Vec::from_iter(items);
|
||||||
|
self.run_on_slice(&mut items, f);
|
||||||
|
items
|
||||||
|
}
|
||||||
|
pub fn run_on_slice<T>(self, items: &mut [T], mut f: impl FnMut(&T, &T) -> T) -> &mut [T] {
|
||||||
|
self.ops(items.len()).into_iter().for_each(
|
||||||
|
|PrefixSumOp {
|
||||||
|
lhs_index,
|
||||||
|
rhs_and_dest_index,
|
||||||
|
row: _,
|
||||||
|
}| {
|
||||||
|
items[rhs_and_dest_index.get()] =
|
||||||
|
f(&items[lhs_index], &items[rhs_and_dest_index.get()]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
items
|
||||||
|
}
|
||||||
|
pub fn filtered_ops(
|
||||||
|
self,
|
||||||
|
item_live_out_flags: impl IntoIterator<Item = bool>,
|
||||||
|
) -> Vec<PrefixSumOp> {
|
||||||
|
let mut item_live_out_flags = Vec::from_iter(item_live_out_flags);
|
||||||
|
let prefix_sum_ops = self.ops(item_live_out_flags.len());
|
||||||
|
let mut ops_live_flags = vec![false; prefix_sum_ops.len()];
|
||||||
|
for (
|
||||||
|
op_index,
|
||||||
|
&PrefixSumOp {
|
||||||
|
lhs_index,
|
||||||
|
rhs_and_dest_index,
|
||||||
|
row: _,
|
||||||
|
},
|
||||||
|
) in prefix_sum_ops.iter().enumerate().rev()
|
||||||
|
{
|
||||||
|
let live = item_live_out_flags[rhs_and_dest_index.get()];
|
||||||
|
item_live_out_flags[lhs_index] |= live;
|
||||||
|
ops_live_flags[op_index] = live;
|
||||||
|
}
|
||||||
|
prefix_sum_ops
|
||||||
|
.into_iter()
|
||||||
|
.zip(ops_live_flags)
|
||||||
|
.filter_map(|(op, live)| live.then_some(op))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
pub fn reduce_ops(self, item_count: usize) -> Interned<[PrefixSumOp]> {
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
struct MyMemoize(PrefixSumAlgorithm);
|
||||||
|
impl Memoize for MyMemoize {
|
||||||
|
type Input = usize;
|
||||||
|
type InputOwned = usize;
|
||||||
|
type Output = Interned<[PrefixSumOp]>;
|
||||||
|
|
||||||
|
fn inner(self, item_count: &Self::Input) -> Self::Output {
|
||||||
|
let mut item_live_out_flags = vec![false; *item_count];
|
||||||
|
let Some(last_item_live_out_flag) = item_live_out_flags.last_mut() else {
|
||||||
|
return Interned::default();
|
||||||
|
};
|
||||||
|
*last_item_live_out_flag = true;
|
||||||
|
Intern::intern_owned(self.0.filtered_ops(item_live_out_flags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MyMemoize(self).get_owned(item_count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reduce_ops(item_count: usize) -> Interned<[PrefixSumOp]> {
|
||||||
|
PrefixSumAlgorithm::LowLatency.reduce_ops(item_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reduce<T>(items: impl IntoIterator<Item = T>, mut f: impl FnMut(T, T) -> T) -> Option<T> {
|
||||||
|
let mut items: Vec<_> = items.into_iter().map(Some).collect();
|
||||||
|
for op in reduce_ops(items.len()) {
|
||||||
|
let (Some(lhs), Some(rhs)) = (
|
||||||
|
items[op.lhs_index].take(),
|
||||||
|
items[op.rhs_and_dest_index.get()].take(),
|
||||||
|
) else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
items[op.rhs_and_dest_index.get()] = Some(f(lhs, rhs));
|
||||||
|
}
|
||||||
|
items.last_mut().and_then(Option::take)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn input_strings() -> [String; 9] {
|
||||||
|
std::array::from_fn(|i| String::from_utf8(vec![b'a' + i as u8]).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prefix_sum_strings() {
|
||||||
|
let input = input_strings();
|
||||||
|
let expected: Vec<String> = input
|
||||||
|
.iter()
|
||||||
|
.scan(String::new(), |l, r| {
|
||||||
|
*l += r;
|
||||||
|
Some(l.clone())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
println!("expected: {expected:?}");
|
||||||
|
assert_eq!(
|
||||||
|
*PrefixSumAlgorithm::WorkEfficient
|
||||||
|
.run_on_slice(&mut input.clone(), |l, r| l.to_string() + r),
|
||||||
|
*expected
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*PrefixSumAlgorithm::LowLatency
|
||||||
|
.run_on_slice(&mut input.clone(), |l, r| l.to_string() + r),
|
||||||
|
*expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reduce_string() {
|
||||||
|
let input = input_strings();
|
||||||
|
let expected = input.clone().into_iter().reduce(|l, r| l + &r);
|
||||||
|
assert_eq!(reduce(input, |l, r| l + &r), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op(lhs_index: usize, rhs_and_dest_index: usize, row: u32) -> PrefixSumOp {
|
||||||
|
PrefixSumOp {
|
||||||
|
lhs_index,
|
||||||
|
rhs_and_dest_index: NonZeroUsize::new(rhs_and_dest_index).expect("should be non-zero"),
|
||||||
|
row,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reduce_ops_9() {
|
||||||
|
let expected = vec![
|
||||||
|
op(7, 8, 0),
|
||||||
|
op(5, 6, 0),
|
||||||
|
op(3, 4, 0),
|
||||||
|
op(1, 2, 0),
|
||||||
|
op(6, 8, 1),
|
||||||
|
op(2, 4, 1),
|
||||||
|
op(4, 8, 2),
|
||||||
|
op(0, 8, 3),
|
||||||
|
];
|
||||||
|
println!("expected: {expected:#?}");
|
||||||
|
let ops = reduce_ops(9);
|
||||||
|
println!("ops: {ops:#?}");
|
||||||
|
assert_eq!(*ops, *expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reduce_ops_8() {
|
||||||
|
let expected = vec![
|
||||||
|
op(6, 7, 0),
|
||||||
|
op(4, 5, 0),
|
||||||
|
op(2, 3, 0),
|
||||||
|
op(0, 1, 0),
|
||||||
|
op(5, 7, 1),
|
||||||
|
op(1, 3, 1),
|
||||||
|
op(3, 7, 2),
|
||||||
|
];
|
||||||
|
println!("expected: {expected:#?}");
|
||||||
|
let ops = reduce_ops(8);
|
||||||
|
println!("ops: {ops:#?}");
|
||||||
|
assert_eq!(*ops, *expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_count_ones() {
|
||||||
|
for width in 0..=10u32 {
|
||||||
|
for v in 0..1u32 << width {
|
||||||
|
let expected = v.count_ones();
|
||||||
|
assert_eq!(
|
||||||
|
reduce((0..width).map(|i| (v >> i) & 1), |l, r| l + r).unwrap_or(0),
|
||||||
|
expected,
|
||||||
|
"v={v:#X}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn test_diagram(ops: impl IntoIterator<Item = PrefixSumOp>, item_count: usize, expected: &str) {
|
||||||
|
let text = PrefixSumOp::diagram_with_config(
|
||||||
|
ops,
|
||||||
|
item_count,
|
||||||
|
DiagramConfig {
|
||||||
|
plus: Cow::Borrowed("@"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
println!("text:\n{text}\n");
|
||||||
|
assert_eq!(text, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_work_efficient_diagram_16() {
|
||||||
|
let item_count = 16;
|
||||||
|
test_diagram(
|
||||||
|
PrefixSumAlgorithm::WorkEfficient.ops(item_count),
|
||||||
|
item_count,
|
||||||
|
&r"
|
||||||
|
| | | | | | | | | | | | | | | |
|
||||||
|
● | ● | ● | ● | ● | ● | ● | ● |
|
||||||
|
|\ | |\ | |\ | |\ | |\ | |\ | |\ | |\ |
|
||||||
|
| \| | \| | \| | \| | \| | \| | \| | \|
|
||||||
|
| @ | @ | @ | @ | @ | @ | @ | @
|
||||||
|
| |\ | | | |\ | | | |\ | | | |\ | |
|
||||||
|
| | \| | | | \| | | | \| | | | \| |
|
||||||
|
| | X | | | X | | | X | | | X |
|
||||||
|
| | |\ | | | |\ | | | |\ | | | |\ |
|
||||||
|
| | | \| | | | \| | | | \| | | | \|
|
||||||
|
| | | @ | | | @ | | | @ | | | @
|
||||||
|
| | | |\ | | | | | | | |\ | | | |
|
||||||
|
| | | | \| | | | | | | | \| | | |
|
||||||
|
| | | | X | | | | | | | X | | |
|
||||||
|
| | | | |\ | | | | | | | |\ | | |
|
||||||
|
| | | | | \| | | | | | | | \| | |
|
||||||
|
| | | | | X | | | | | | | X | |
|
||||||
|
| | | | | |\ | | | | | | | |\ | |
|
||||||
|
| | | | | | \| | | | | | | | \| |
|
||||||
|
| | | | | | X | | | | | | | X |
|
||||||
|
| | | | | | |\ | | | | | | | |\ |
|
||||||
|
| | | | | | | \| | | | | | | | \|
|
||||||
|
| | | | | | | @ | | | | | | | @
|
||||||
|
| | | | | | | |\ | | | | | | | |
|
||||||
|
| | | | | | | | \| | | | | | | |
|
||||||
|
| | | | | | | | X | | | | | | |
|
||||||
|
| | | | | | | | |\ | | | | | | |
|
||||||
|
| | | | | | | | | \| | | | | | |
|
||||||
|
| | | | | | | | | X | | | | | |
|
||||||
|
| | | | | | | | | |\ | | | | | |
|
||||||
|
| | | | | | | | | | \| | | | | |
|
||||||
|
| | | | | | | | | | X | | | | |
|
||||||
|
| | | | | | | | | | |\ | | | | |
|
||||||
|
| | | | | | | | | | | \| | | | |
|
||||||
|
| | | | | | | | | | | X | | | |
|
||||||
|
| | | | | | | | | | | |\ | | | |
|
||||||
|
| | | | | | | | | | | | \| | | |
|
||||||
|
| | | | | | | | | | | | X | | |
|
||||||
|
| | | | | | | | | | | | |\ | | |
|
||||||
|
| | | | | | | | | | | | | \| | |
|
||||||
|
| | | | | | | | | | | | | X | |
|
||||||
|
| | | | | | | | | | | | | |\ | |
|
||||||
|
| | | | | | | | | | | | | | \| |
|
||||||
|
| | | | | | | | | | | | | | X |
|
||||||
|
| | | | | | | | | | | | | | |\ |
|
||||||
|
| | | | | | | | | | | | | | | \|
|
||||||
|
| | | | | | | ● | | | | | | | @
|
||||||
|
| | | | | | | |\ | | | | | | | |
|
||||||
|
| | | | | | | | \| | | | | | | |
|
||||||
|
| | | | | | | | X | | | | | | |
|
||||||
|
| | | | | | | | |\ | | | | | | |
|
||||||
|
| | | | | | | | | \| | | | | | |
|
||||||
|
| | | | | | | | | X | | | | | |
|
||||||
|
| | | | | | | | | |\ | | | | | |
|
||||||
|
| | | | | | | | | | \| | | | | |
|
||||||
|
| | | | | | | | | | X | | | | |
|
||||||
|
| | | | | | | | | | |\ | | | | |
|
||||||
|
| | | | | | | | | | | \| | | | |
|
||||||
|
| | | ● | | | ● | | | @ | | | |
|
||||||
|
| | | |\ | | | |\ | | | |\ | | | |
|
||||||
|
| | | | \| | | | \| | | | \| | | |
|
||||||
|
| | | | X | | | X | | | X | | |
|
||||||
|
| | | | |\ | | | |\ | | | |\ | | |
|
||||||
|
| | | | | \| | | | \| | | | \| | |
|
||||||
|
| ● | ● | @ | ● | @ | ● | @ | |
|
||||||
|
| |\ | |\ | |\ | |\ | |\ | |\ | |\ | |
|
||||||
|
| | \| | \| | \| | \| | \| | \| | \| |
|
||||||
|
| | @ | @ | @ | @ | @ | @ | @ |
|
||||||
|
| | | | | | | | | | | | | | | |
|
||||||
|
"[1..], // trim newline at start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_low_latency_diagram_16() {
|
||||||
|
let item_count = 16;
|
||||||
|
test_diagram(
|
||||||
|
PrefixSumAlgorithm::LowLatency.ops(item_count),
|
||||||
|
item_count,
|
||||||
|
&r"
|
||||||
|
| | | | | | | | | | | | | | | |
|
||||||
|
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● |
|
||||||
|
|\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |
|
||||||
|
| \| \| \| \| \| \| \| \| \| \| \| \| \| \| \|
|
||||||
|
● @ @ @ @ @ @ @ @ @ @ @ @ @ @ @
|
||||||
|
|\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | |
|
||||||
|
| \| \| \| \| \| \| \| \| \| \| \| \| \| \| |
|
||||||
|
| X X X X X X X X X X X X X X |
|
||||||
|
| |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |
|
||||||
|
| | \| \| \| \| \| \| \| \| \| \| \| \| \| \|
|
||||||
|
● ● @ @ @ @ @ @ @ @ @ @ @ @ @ @
|
||||||
|
|\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | | |
|
||||||
|
| \| \| \| \| \| \| \| \| \| \| \| \| | | |
|
||||||
|
| X X X X X X X X X X X X | | |
|
||||||
|
| |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | |
|
||||||
|
| | \| \| \| \| \| \| \| \| \| \| \| \| | |
|
||||||
|
| | X X X X X X X X X X X X | |
|
||||||
|
| | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | |
|
||||||
|
| | | \| \| \| \| \| \| \| \| \| \| \| \| |
|
||||||
|
| | | X X X X X X X X X X X X |
|
||||||
|
| | | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |
|
||||||
|
| | | | \| \| \| \| \| \| \| \| \| \| \| \|
|
||||||
|
● ● ● ● @ @ @ @ @ @ @ @ @ @ @ @
|
||||||
|
|\ |\ |\ |\ |\ |\ |\ |\ | | | | | | | |
|
||||||
|
| \| \| \| \| \| \| \| \| | | | | | | |
|
||||||
|
| X X X X X X X X | | | | | | |
|
||||||
|
| |\ |\ |\ |\ |\ |\ |\ |\ | | | | | | |
|
||||||
|
| | \| \| \| \| \| \| \| \| | | | | | |
|
||||||
|
| | X X X X X X X X | | | | | |
|
||||||
|
| | |\ |\ |\ |\ |\ |\ |\ |\ | | | | | |
|
||||||
|
| | | \| \| \| \| \| \| \| \| | | | | |
|
||||||
|
| | | X X X X X X X X | | | | |
|
||||||
|
| | | |\ |\ |\ |\ |\ |\ |\ |\ | | | | |
|
||||||
|
| | | | \| \| \| \| \| \| \| \| | | | |
|
||||||
|
| | | | X X X X X X X X | | | |
|
||||||
|
| | | | |\ |\ |\ |\ |\ |\ |\ |\ | | | |
|
||||||
|
| | | | | \| \| \| \| \| \| \| \| | | |
|
||||||
|
| | | | | X X X X X X X X | | |
|
||||||
|
| | | | | |\ |\ |\ |\ |\ |\ |\ |\ | | |
|
||||||
|
| | | | | | \| \| \| \| \| \| \| \| | |
|
||||||
|
| | | | | | X X X X X X X X | |
|
||||||
|
| | | | | | |\ |\ |\ |\ |\ |\ |\ |\ | |
|
||||||
|
| | | | | | | \| \| \| \| \| \| \| \| |
|
||||||
|
| | | | | | | X X X X X X X X |
|
||||||
|
| | | | | | | |\ |\ |\ |\ |\ |\ |\ |\ |
|
||||||
|
| | | | | | | | \| \| \| \| \| \| \| \|
|
||||||
|
| | | | | | | | @ @ @ @ @ @ @ @
|
||||||
|
| | | | | | | | | | | | | | | |
|
||||||
|
"[1..], // trim newline at start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_work_efficient_diagram_9() {
|
||||||
|
let item_count = 9;
|
||||||
|
test_diagram(
|
||||||
|
PrefixSumAlgorithm::WorkEfficient.ops(item_count),
|
||||||
|
item_count,
|
||||||
|
&r"
|
||||||
|
| | | | | | | | |
|
||||||
|
● | ● | ● | ● | |
|
||||||
|
|\ | |\ | |\ | |\ | |
|
||||||
|
| \| | \| | \| | \| |
|
||||||
|
| @ | @ | @ | @ |
|
||||||
|
| |\ | | | |\ | | |
|
||||||
|
| | \| | | | \| | |
|
||||||
|
| | X | | | X | |
|
||||||
|
| | |\ | | | |\ | |
|
||||||
|
| | | \| | | | \| |
|
||||||
|
| | | @ | | | @ |
|
||||||
|
| | | |\ | | | | |
|
||||||
|
| | | | \| | | | |
|
||||||
|
| | | | X | | | |
|
||||||
|
| | | | |\ | | | |
|
||||||
|
| | | | | \| | | |
|
||||||
|
| | | | | X | | |
|
||||||
|
| | | | | |\ | | |
|
||||||
|
| | | | | | \| | |
|
||||||
|
| | | | | | X | |
|
||||||
|
| | | | | | |\ | |
|
||||||
|
| | | | | | | \| |
|
||||||
|
| | | ● | | | @ |
|
||||||
|
| | | |\ | | | | |
|
||||||
|
| | | | \| | | | |
|
||||||
|
| | | | X | | | |
|
||||||
|
| | | | |\ | | | |
|
||||||
|
| | | | | \| | | |
|
||||||
|
| ● | ● | @ | ● |
|
||||||
|
| |\ | |\ | |\ | |\ |
|
||||||
|
| | \| | \| | \| | \|
|
||||||
|
| | @ | @ | @ | @
|
||||||
|
| | | | | | | | |
|
||||||
|
"[1..], // trim newline at start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_low_latency_diagram_9() {
|
||||||
|
let item_count = 9;
|
||||||
|
test_diagram(
|
||||||
|
PrefixSumAlgorithm::LowLatency.ops(item_count),
|
||||||
|
item_count,
|
||||||
|
&r"
|
||||||
|
| | | | | | | | |
|
||||||
|
● ● ● ● ● ● ● ● |
|
||||||
|
|\ |\ |\ |\ |\ |\ |\ |\ |
|
||||||
|
| \| \| \| \| \| \| \| \|
|
||||||
|
● @ @ @ @ @ @ @ @
|
||||||
|
|\ |\ |\ |\ |\ |\ |\ | |
|
||||||
|
| \| \| \| \| \| \| \| |
|
||||||
|
| X X X X X X X |
|
||||||
|
| |\ |\ |\ |\ |\ |\ |\ |
|
||||||
|
| | \| \| \| \| \| \| \|
|
||||||
|
● ● @ @ @ @ @ @ @
|
||||||
|
|\ |\ |\ |\ |\ | | | |
|
||||||
|
| \| \| \| \| \| | | |
|
||||||
|
| X X X X X | | |
|
||||||
|
| |\ |\ |\ |\ |\ | | |
|
||||||
|
| | \| \| \| \| \| | |
|
||||||
|
| | X X X X X | |
|
||||||
|
| | |\ |\ |\ |\ |\ | |
|
||||||
|
| | | \| \| \| \| \| |
|
||||||
|
| | | X X X X X |
|
||||||
|
| | | |\ |\ |\ |\ |\ |
|
||||||
|
| | | | \| \| \| \| \|
|
||||||
|
● | | | @ @ @ @ @
|
||||||
|
|\ | | | | | | | |
|
||||||
|
| \| | | | | | | |
|
||||||
|
| X | | | | | | |
|
||||||
|
| |\ | | | | | | |
|
||||||
|
| | \| | | | | | |
|
||||||
|
| | X | | | | | |
|
||||||
|
| | |\ | | | | | |
|
||||||
|
| | | \| | | | | |
|
||||||
|
| | | X | | | | |
|
||||||
|
| | | |\ | | | | |
|
||||||
|
| | | | \| | | | |
|
||||||
|
| | | | X | | | |
|
||||||
|
| | | | |\ | | | |
|
||||||
|
| | | | | \| | | |
|
||||||
|
| | | | | X | | |
|
||||||
|
| | | | | |\ | | |
|
||||||
|
| | | | | | \| | |
|
||||||
|
| | | | | | X | |
|
||||||
|
| | | | | | |\ | |
|
||||||
|
| | | | | | | \| |
|
||||||
|
| | | | | | | X |
|
||||||
|
| | | | | | | |\ |
|
||||||
|
| | | | | | | | \|
|
||||||
|
| | | | | | | | @
|
||||||
|
| | | | | | | | |
|
||||||
|
"[1..], // trim newline at start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reduce_diagram_16() {
|
||||||
|
let item_count = 16;
|
||||||
|
test_diagram(
|
||||||
|
reduce_ops(item_count),
|
||||||
|
item_count,
|
||||||
|
&r"
|
||||||
|
| | | | | | | | | | | | | | | |
|
||||||
|
● | ● | ● | ● | ● | ● | ● | ● |
|
||||||
|
|\ | |\ | |\ | |\ | |\ | |\ | |\ | |\ |
|
||||||
|
| \| | \| | \| | \| | \| | \| | \| | \|
|
||||||
|
| @ | @ | @ | @ | @ | @ | @ | @
|
||||||
|
| |\ | | | |\ | | | |\ | | | |\ | |
|
||||||
|
| | \| | | | \| | | | \| | | | \| |
|
||||||
|
| | X | | | X | | | X | | | X |
|
||||||
|
| | |\ | | | |\ | | | |\ | | | |\ |
|
||||||
|
| | | \| | | | \| | | | \| | | | \|
|
||||||
|
| | | @ | | | @ | | | @ | | | @
|
||||||
|
| | | |\ | | | | | | | |\ | | | |
|
||||||
|
| | | | \| | | | | | | | \| | | |
|
||||||
|
| | | | X | | | | | | | X | | |
|
||||||
|
| | | | |\ | | | | | | | |\ | | |
|
||||||
|
| | | | | \| | | | | | | | \| | |
|
||||||
|
| | | | | X | | | | | | | X | |
|
||||||
|
| | | | | |\ | | | | | | | |\ | |
|
||||||
|
| | | | | | \| | | | | | | | \| |
|
||||||
|
| | | | | | X | | | | | | | X |
|
||||||
|
| | | | | | |\ | | | | | | | |\ |
|
||||||
|
| | | | | | | \| | | | | | | | \|
|
||||||
|
| | | | | | | @ | | | | | | | @
|
||||||
|
| | | | | | | |\ | | | | | | | |
|
||||||
|
| | | | | | | | \| | | | | | | |
|
||||||
|
| | | | | | | | X | | | | | | |
|
||||||
|
| | | | | | | | |\ | | | | | | |
|
||||||
|
| | | | | | | | | \| | | | | | |
|
||||||
|
| | | | | | | | | X | | | | | |
|
||||||
|
| | | | | | | | | |\ | | | | | |
|
||||||
|
| | | | | | | | | | \| | | | | |
|
||||||
|
| | | | | | | | | | X | | | | |
|
||||||
|
| | | | | | | | | | |\ | | | | |
|
||||||
|
| | | | | | | | | | | \| | | | |
|
||||||
|
| | | | | | | | | | | X | | | |
|
||||||
|
| | | | | | | | | | | |\ | | | |
|
||||||
|
| | | | | | | | | | | | \| | | |
|
||||||
|
| | | | | | | | | | | | X | | |
|
||||||
|
| | | | | | | | | | | | |\ | | |
|
||||||
|
| | | | | | | | | | | | | \| | |
|
||||||
|
| | | | | | | | | | | | | X | |
|
||||||
|
| | | | | | | | | | | | | |\ | |
|
||||||
|
| | | | | | | | | | | | | | \| |
|
||||||
|
| | | | | | | | | | | | | | X |
|
||||||
|
| | | | | | | | | | | | | | |\ |
|
||||||
|
| | | | | | | | | | | | | | | \|
|
||||||
|
| | | | | | | | | | | | | | | @
|
||||||
|
| | | | | | | | | | | | | | | |
|
||||||
|
"[1..], // trim newline at start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reduce_diagram_9() {
|
||||||
|
let item_count = 9;
|
||||||
|
test_diagram(
|
||||||
|
reduce_ops(item_count),
|
||||||
|
item_count,
|
||||||
|
&r"
|
||||||
|
| | | | | | | | |
|
||||||
|
| ● | ● | ● | ● |
|
||||||
|
| |\ | |\ | |\ | |\ |
|
||||||
|
| | \| | \| | \| | \|
|
||||||
|
| | @ | @ | @ | @
|
||||||
|
| | |\ | | | |\ | |
|
||||||
|
| | | \| | | | \| |
|
||||||
|
| | | X | | | X |
|
||||||
|
| | | |\ | | | |\ |
|
||||||
|
| | | | \| | | | \|
|
||||||
|
| | | | @ | | | @
|
||||||
|
| | | | |\ | | | |
|
||||||
|
| | | | | \| | | |
|
||||||
|
| | | | | X | | |
|
||||||
|
| | | | | |\ | | |
|
||||||
|
| | | | | | \| | |
|
||||||
|
| | | | | | X | |
|
||||||
|
| | | | | | |\ | |
|
||||||
|
| | | | | | | \| |
|
||||||
|
| | | | | | | X |
|
||||||
|
| | | | | | | |\ |
|
||||||
|
| | | | | | | | \|
|
||||||
|
● | | | | | | | @
|
||||||
|
|\ | | | | | | | |
|
||||||
|
| \| | | | | | | |
|
||||||
|
| X | | | | | | |
|
||||||
|
| |\ | | | | | | |
|
||||||
|
| | \| | | | | | |
|
||||||
|
| | X | | | | | |
|
||||||
|
| | |\ | | | | | |
|
||||||
|
| | | \| | | | | |
|
||||||
|
| | | X | | | | |
|
||||||
|
| | | |\ | | | | |
|
||||||
|
| | | | \| | | | |
|
||||||
|
| | | | X | | | |
|
||||||
|
| | | | |\ | | | |
|
||||||
|
| | | | | \| | | |
|
||||||
|
| | | | | X | | |
|
||||||
|
| | | | | |\ | | |
|
||||||
|
| | | | | | \| | |
|
||||||
|
| | | | | | X | |
|
||||||
|
| | | | | | |\ | |
|
||||||
|
| | | | | | | \| |
|
||||||
|
| | | | | | | X |
|
||||||
|
| | | | | | | |\ |
|
||||||
|
| | | | | | | | \|
|
||||||
|
| | | | | | | | @
|
||||||
|
| | | | | | | | |
|
||||||
|
"[1..], // trim newline at start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue