add efficient prefix-sums and reductions #22
|
@ -29,4 +29,5 @@ pub use misc::{
|
|||
};
|
||||
|
||||
pub mod job_server;
|
||||
pub mod prefix_sum;
|
||||
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