forked from libre-chip/fayalite
		
	add efficient prefix-sums and reductions
This commit is contained in:
		
							parent
							
								
									50c86e18dc
								
							
						
					
					
						commit
						bd75fdfefd
					
				
					 2 changed files with 840 additions and 0 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue