From a2ebe957397f70cd08d9a2924b07bb71b6e95893 Mon Sep 17 00:00:00 2001 From: Klas Segeljakt Date: Sat, 18 Jul 2020 20:57:46 +0200 Subject: [PATCH 1/5] Add Rust implementation for DABA --- rust/src/daba/mod.rs | 151 ++++++++++++++++++++++++++++++++++++++ rust/src/lib.rs | 2 + rust/tests/fifo_window.rs | 12 +-- 3 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 rust/src/daba/mod.rs diff --git a/rust/src/daba/mod.rs b/rust/src/daba/mod.rs new file mode 100644 index 0000000..f1399a4 --- /dev/null +++ b/rust/src/daba/mod.rs @@ -0,0 +1,151 @@ +use crate::FifoWindow; +use alga::general::AbstractMonoid; +use alga::general::Operator; +use std::collections::VecDeque; +use std::marker::PhantomData; + +#[derive(Clone)] +pub struct DABA +where + Value: AbstractMonoid + Clone, + BinOp: Operator, +{ + // ith oldest value in FIFO order stored at vi = vals[i] + vals: VecDeque, + aggs: VecDeque, + // 0 ≤ l ≤ r ≤ a ≤ b ≤ aggs.len() + l: usize, // Left, ∀p ∈ l...r−1 : aggs[p] = vals[p] ⊕ ... ⊕ vals[r−1] + r: usize, // Right, ∀p ∈ r...a−1 : aggs[p] = vals[R] ⊕ ... ⊕ vals[p] + a: usize, // Accum, ∀p ∈ a...b−1 : aggs[p] = vals[p] ⊕ ... ⊕ vals[b−1] + b: usize, // Back, ∀p ∈ b...e−1 : aggs[p] = vals[B] ⊕ ... ⊕ vals[p] + op: PhantomData, +} + +impl FifoWindow for DABA +where + Value: AbstractMonoid + Clone, + BinOp: Operator, +{ + fn new() -> Self { + Self { + vals: VecDeque::new(), + aggs: VecDeque::new(), + l: 0, + r: 0, + a: 0, + b: 0, + op: PhantomData, + } + } + fn push(&mut self, v: Value) { + self.aggs.push_back(self.agg_b().operate(&v)); + self.vals.push_back(v); + self.fixup(); + } + fn pop(&mut self) { + if self.vals.pop_front().is_some() { + self.aggs.pop_front(); + self.l -= 1; + self.r -= 1; + self.a -= 1; + self.b -= 1; + self.fixup(); + } + } + fn query(&self) -> Value { + self.agg_f().operate(&self.agg_b()) + } + fn len(&self) -> usize { + self.vals.len() + } + fn is_empty(&self) -> bool { + self.vals.is_empty() + } +} + +impl DABA +where + Value: AbstractMonoid + Clone, + BinOp: Operator, +{ + #[inline(always)] + fn agg_f(&self) -> Value { + if self.aggs.is_empty() { + Value::identity() + } else { + self.aggs.front().unwrap().clone() + } + } + #[inline(always)] + fn agg_b(&self) -> Value { + if self.b == self.aggs.len() { + Value::identity() + } else { + self.aggs.back().unwrap().clone() + } + } + #[inline(always)] + fn agg_l(&self) -> Value { + if self.l == self.r { + Value::identity() + } else { + self.aggs[self.l].clone() + } + } + #[inline(always)] + fn agg_r(&self) -> Value { + if self.r == self.a { + Value::identity() + } else { + self.aggs[self.a - 1].clone() + } + } + #[inline(always)] + fn agg_a(&self) -> Value { + if self.a == self.b { + Value::identity() + } else { + self.aggs[self.a].clone() + } + } + fn fixup(&mut self) { + if self.b == 0 { + self.singleton() + } else { + if self.l == self.b { + self.flip() + } + if self.l == self.r { + self.shift() + } else { + self.shrink() + } + } + } + #[inline(always)] + fn singleton(&mut self) { + self.l = self.aggs.len(); + self.r = self.l; + self.a = self.l; + self.b = self.l; + } + #[inline(always)] + fn flip(&mut self) { + self.l = 0; + self.a = self.aggs.len(); + self.b = self.a; + } + #[inline(always)] + fn shift(&mut self) { + self.a += 1; + self.r += 1; + self.l += 1; + } + #[inline(always)] + fn shrink(&mut self) { + self.aggs[self.l] = self.agg_l().operate(&self.agg_r()).operate(&self.agg_a()); + self.l += 1; + self.aggs[self.a - 1] = self.vals[self.a - 1].operate(&self.agg_a()); + self.a -= 1; + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4db68e3..e33f065 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -57,3 +57,5 @@ pub mod two_stacks; /// Reactive-Aggregator pub mod reactive; +/// De-Amortized Banker's Aggregator +pub mod daba; diff --git a/rust/tests/fifo_window.rs b/rust/tests/fifo_window.rs index c97f710..5facd1c 100644 --- a/rust/tests/fifo_window.rs +++ b/rust/tests/fifo_window.rs @@ -141,10 +141,10 @@ where } test_matrix! { - test1 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks ], - test2 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks ], - test3 => [ recalc::ReCalc, reactive::Reactive, two_stacks::TwoStacks ], - test4 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks ], - test5 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks ], - test6 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks ] + test1 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks, daba::DABA ], + test2 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks, daba::DABA ], + test3 => [ recalc::ReCalc, reactive::Reactive, two_stacks::TwoStacks, daba::DABA ], + test4 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks, daba::DABA ], + test5 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks, daba::DABA ], + test6 => [ recalc::ReCalc, soe::SoE, reactive::Reactive, two_stacks::TwoStacks, daba::DABA ] } From 0ba8f1e542517180bd05249ef41f48131e192d4c Mon Sep 17 00:00:00 2001 From: Klas Segeljakt Date: Mon, 20 Jul 2020 17:35:27 +0200 Subject: [PATCH 2/5] Merge VecDeques into one --- rust/src/daba/mod.rs | 61 +++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/rust/src/daba/mod.rs b/rust/src/daba/mod.rs index f1399a4..d42ebd2 100644 --- a/rust/src/daba/mod.rs +++ b/rust/src/daba/mod.rs @@ -4,6 +4,18 @@ use alga::general::Operator; use std::collections::VecDeque; use std::marker::PhantomData; +#[derive(Clone)] +struct Item { + val: Value, + agg: Value, +} + +impl Item { + fn new(val: Value, agg: Value) -> Self { + Self { val, agg } + } +} + #[derive(Clone)] pub struct DABA where @@ -11,13 +23,12 @@ where BinOp: Operator, { // ith oldest value in FIFO order stored at vi = vals[i] - vals: VecDeque, - aggs: VecDeque, - // 0 ≤ l ≤ r ≤ a ≤ b ≤ aggs.len() - l: usize, // Left, ∀p ∈ l...r−1 : aggs[p] = vals[p] ⊕ ... ⊕ vals[r−1] - r: usize, // Right, ∀p ∈ r...a−1 : aggs[p] = vals[R] ⊕ ... ⊕ vals[p] - a: usize, // Accum, ∀p ∈ a...b−1 : aggs[p] = vals[p] ⊕ ... ⊕ vals[b−1] - b: usize, // Back, ∀p ∈ b...e−1 : aggs[p] = vals[B] ⊕ ... ⊕ vals[p] + items: VecDeque>, + // 0 ≤ l ≤ r ≤ a ≤ b ≤ items.len() + l: usize, // Left, ∀p ∈ l...r−1 : items[p].agg = items[p].val ⊕ ... ⊕ items[r−1].val + r: usize, // Right, ∀p ∈ r...a−1 : items[p].agg = items[R].val ⊕ ... ⊕ items[p].val + a: usize, // Accum, ∀p ∈ a...b−1 : items[p].agg = items[p].val ⊕ ... ⊕ items[b−1].val + b: usize, // Back, ∀p ∈ b...e−1 : items[p].agg = items[B].val ⊕ ... ⊕ items[p].val op: PhantomData, } @@ -28,8 +39,7 @@ where { fn new() -> Self { Self { - vals: VecDeque::new(), - aggs: VecDeque::new(), + items: VecDeque::new(), l: 0, r: 0, a: 0, @@ -38,13 +48,12 @@ where } } fn push(&mut self, v: Value) { - self.aggs.push_back(self.agg_b().operate(&v)); - self.vals.push_back(v); + let agg = self.agg_b().operate(&v); + self.items.push_back(Item::new(v, agg)); self.fixup(); } fn pop(&mut self) { - if self.vals.pop_front().is_some() { - self.aggs.pop_front(); + if self.items.pop_front().is_some() { self.l -= 1; self.r -= 1; self.a -= 1; @@ -56,10 +65,10 @@ where self.agg_f().operate(&self.agg_b()) } fn len(&self) -> usize { - self.vals.len() + self.items.len() } fn is_empty(&self) -> bool { - self.vals.is_empty() + self.items.is_empty() } } @@ -70,18 +79,18 @@ where { #[inline(always)] fn agg_f(&self) -> Value { - if self.aggs.is_empty() { + if self.items.is_empty() { Value::identity() } else { - self.aggs.front().unwrap().clone() + self.items.front().unwrap().agg.clone() } } #[inline(always)] fn agg_b(&self) -> Value { - if self.b == self.aggs.len() { + if self.b == self.items.len() { Value::identity() } else { - self.aggs.back().unwrap().clone() + self.items.back().unwrap().agg.clone() } } #[inline(always)] @@ -89,7 +98,7 @@ where if self.l == self.r { Value::identity() } else { - self.aggs[self.l].clone() + self.items[self.l].agg.clone() } } #[inline(always)] @@ -97,7 +106,7 @@ where if self.r == self.a { Value::identity() } else { - self.aggs[self.a - 1].clone() + self.items[self.a - 1].agg.clone() } } #[inline(always)] @@ -105,7 +114,7 @@ where if self.a == self.b { Value::identity() } else { - self.aggs[self.a].clone() + self.items[self.a].agg.clone() } } fn fixup(&mut self) { @@ -124,7 +133,7 @@ where } #[inline(always)] fn singleton(&mut self) { - self.l = self.aggs.len(); + self.l = self.items.len(); self.r = self.l; self.a = self.l; self.b = self.l; @@ -132,7 +141,7 @@ where #[inline(always)] fn flip(&mut self) { self.l = 0; - self.a = self.aggs.len(); + self.a = self.items.len(); self.b = self.a; } #[inline(always)] @@ -143,9 +152,9 @@ where } #[inline(always)] fn shrink(&mut self) { - self.aggs[self.l] = self.agg_l().operate(&self.agg_r()).operate(&self.agg_a()); + self.items[self.l].agg = self.agg_l().operate(&self.agg_r()).operate(&self.agg_a()); self.l += 1; - self.aggs[self.a - 1] = self.vals[self.a - 1].operate(&self.agg_a()); + self.items[self.a - 1].agg = self.items[self.a - 1].val.operate(&self.agg_a()); self.a -= 1; } } From 94c04004c2222652d9a729543882040c7b34d17b Mon Sep 17 00:00:00 2001 From: Klas Segeljakt Date: Mon, 20 Jul 2020 20:49:14 +0200 Subject: [PATCH 3/5] Add #[inline(always)] --- rust/src/daba/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/src/daba/mod.rs b/rust/src/daba/mod.rs index d42ebd2..bef4cc0 100644 --- a/rust/src/daba/mod.rs +++ b/rust/src/daba/mod.rs @@ -117,6 +117,7 @@ where self.items[self.a].agg.clone() } } + #[inline(always)] fn fixup(&mut self) { if self.b == 0 { self.singleton() From f49672e929cc9228c47b2c3796abfdec3bc37421 Mon Sep 17 00:00:00 2001 From: Klas Segeljakt Date: Thu, 23 Jul 2020 11:50:45 +0200 Subject: [PATCH 4/5] Add Rust implementation of Chunked Array Queue --- rust/src/daba/chunked_array_queue.rs | 192 +++++++++++++++++++++++++++ rust/src/daba/mod.rs | 1 + rust/src/lib.rs | 5 + 3 files changed, 198 insertions(+) create mode 100644 rust/src/daba/chunked_array_queue.rs diff --git a/rust/src/daba/chunked_array_queue.rs b/rust/src/daba/chunked_array_queue.rs new file mode 100644 index 0000000..ab3dfb3 --- /dev/null +++ b/rust/src/daba/chunked_array_queue.rs @@ -0,0 +1,192 @@ +use std::collections::linked_list::Cursor as ChunkCursor; +use std::collections::LinkedList; +use std::mem::MaybeUninit; + +/// An [Unrolled Linked List](https://en.wikipedia.org/wiki/Unrolled_linked_list). +pub struct ChunkedArrayQueue { + chunks: LinkedList>, +} + +struct Chunk { + elems: [MaybeUninit; SIZE], + front: usize, + back: usize, +} + +#[derive(Clone)] +pub(crate) struct Cursor<'i, Elem, const SIZE: usize> { + /// A cursor pointing at a chunk + chunk_cursor: ChunkCursor<'i, Chunk>, + /// An index pointing at an element inside the chunk + elem_index: usize, +} + +impl Chunk +where + Elem: Copy, +{ + fn new_middle() -> Self { + Self { + // Unsafe needed so we don't have to initialize every element of the array + elems: unsafe { std::mem::MaybeUninit::uninit().assume_init() }, + front: SIZE / 2, + back: SIZE / 2, + } + } + fn new_front() -> Self { + Self { + elems: unsafe { std::mem::MaybeUninit::uninit().assume_init() }, + front: SIZE - 1, + back: SIZE - 1, + } + } + fn new_back() -> Self { + Self { + elems: unsafe { std::mem::MaybeUninit::uninit().assume_init() }, + front: 0, + back: 0, + } + } +} + +impl ChunkedArrayQueue +where + Elem: Copy, +{ + pub(crate) fn new() -> Self { + let chunk = Chunk::new_middle(); + let mut chunks = LinkedList::new(); + chunks.push_back(chunk); + Self { chunks } + } + pub(crate) fn push_back(&mut self, v: Elem) { + match self.chunks.back_mut() { + Some(chunk) if chunk.back < SIZE - 1 => { + chunk.back += 1; + chunk.elems[chunk.back] = MaybeUninit::new(v); + } + _ => { + self.chunks.push_back(Chunk::new_back()); + self.push_back(v) + } + } + } + pub(crate) fn push_front(&mut self, v: Elem) { + match self.chunks.front_mut() { + Some(chunk) if chunk.front > 0 => { + chunk.front -= 1; + chunk.elems[chunk.front] = MaybeUninit::new(v); + } + _ => { + self.chunks.push_front(Chunk::new_front()); + self.push_front(v) + } + } + } + pub(crate) fn pop_back(&mut self) -> Option { + if let Some(chunk) = self.chunks.back_mut() { + if chunk.front <= chunk.back { + unsafe { + let val = chunk.elems[chunk.back].assume_init(); + chunk.elems[chunk.back] = MaybeUninit::uninit().assume_init(); + chunk.back -= 1; + if chunk.back == 0 { + self.chunks.pop_back(); + } + Some(val) + } + } else { + None + } + } else { + None + } + } + pub(crate) fn pop_front(&mut self) -> Option { + if let Some(chunk) = self.chunks.front_mut() { + if chunk.front <= chunk.back { + unsafe { + let val = chunk.elems[chunk.front].assume_init(); + chunk.elems[chunk.front] = MaybeUninit::uninit().assume_init(); + chunk.front += 1; + if chunk.front == SIZE { + self.chunks.pop_front(); + } + Some(val) + } + } else { + None + } + } else { + None + } + } + pub(crate) fn index_front(&mut self) -> Cursor<'_, Elem, SIZE> { + let chunk_cursor = self.chunks.cursor_front(); + let elem_index = self.chunks.front().unwrap().front; + Cursor { + chunk_cursor, + elem_index, + } + } + pub(crate) fn index_back(&mut self) -> Cursor<'_, Elem, SIZE> { + let chunk_cursor = self.chunks.cursor_back(); + let elem_index = self.chunks.back().unwrap().back; + Cursor { + chunk_cursor, + elem_index, + } + } +} + +impl<'i, Elem, const SIZE: usize> Cursor<'i, Elem, SIZE> { + pub(crate) fn move_next(&mut self) { + if self.elem_index + 1 < self.chunk_cursor.current().unwrap().front { + self.elem_index += 1; + } else { + self.elem_index = 0; + self.chunk_cursor.move_next() + } + } + pub(crate) fn move_prev(&mut self) { + if self.elem_index < self.chunk_cursor.current().unwrap().back { + self.elem_index -= 1; + } else { + self.elem_index = SIZE - 1; + self.chunk_cursor.move_prev() + } + } + pub(crate) fn get(&mut self) -> &Elem { + // OK because elem should always be initialized at this point + unsafe { &self.chunk_cursor.current().unwrap().elems[self.elem_index].get_ref() } + } + pub(crate) fn set(&mut self, v: Elem) { + // Cursors must be able to mutate elems. Using CursorMut is not possible, + // since the cursor must also be cloneable. Therefore, unsafe is used here. + unsafe { + let ptr = self.chunk_cursor.current().unwrap() as *const Chunk + as *mut Chunk; + (*ptr).elems[self.elem_index] = MaybeUninit::new(v); + } + } +} + +impl<'i, Elem, const SIZE: usize> PartialEq for Cursor<'i, Elem, SIZE> { + fn eq(&self, other: &Self) -> bool { + self.elem_index == other.elem_index + && self.chunk_cursor.index() == other.chunk_cursor.index() + } +} + +#[test] +fn test() { + let mut queue = ChunkedArrayQueue::::new(); + queue.push_front(4); + queue.push_front(4); + queue.push_front(7); + queue.push_back(1); + assert_eq!(queue.pop_front(), Some(7)); + assert_eq!(queue.pop_front(), Some(4)); + assert_eq!(queue.pop_back(), Some(1)); + assert_eq!(queue.pop_front(), Some(4)); +} diff --git a/rust/src/daba/mod.rs b/rust/src/daba/mod.rs index bef4cc0..098fa05 100644 --- a/rust/src/daba/mod.rs +++ b/rust/src/daba/mod.rs @@ -1,3 +1,4 @@ +mod chunked_array_queue; use crate::FifoWindow; use alga::general::AbstractMonoid; use alga::general::Operator; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e33f065..2b1ee1c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,3 +1,8 @@ +#![feature(linked_list_cursors)] +#![feature(maybe_uninit_ref)] +#![feature(maybe_uninit_extra)] +#![feature(const_generics)] +#![allow(incomplete_features)] use alga::general::Operator; use std::ops::Range; From 730481df660717b4885b6f7215d26199ed7228b3 Mon Sep 17 00:00:00 2001 From: Klas Segeljakt Date: Thu, 23 Jul 2020 11:54:41 +0200 Subject: [PATCH 5/5] Refactor: if let => match --- rust/src/daba/chunked_array_queue.rs | 50 +++++++++++----------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/rust/src/daba/chunked_array_queue.rs b/rust/src/daba/chunked_array_queue.rs index ab3dfb3..74bbb57 100644 --- a/rust/src/daba/chunked_array_queue.rs +++ b/rust/src/daba/chunked_array_queue.rs @@ -84,41 +84,31 @@ where } } pub(crate) fn pop_back(&mut self) -> Option { - if let Some(chunk) = self.chunks.back_mut() { - if chunk.front <= chunk.back { - unsafe { - let val = chunk.elems[chunk.back].assume_init(); - chunk.elems[chunk.back] = MaybeUninit::uninit().assume_init(); - chunk.back -= 1; - if chunk.back == 0 { - self.chunks.pop_back(); - } - Some(val) + match self.chunks.back_mut() { + Some(chunk) if chunk.front <= chunk.back => unsafe { + let val = chunk.elems[chunk.back].assume_init(); + chunk.elems[chunk.back] = MaybeUninit::uninit().assume_init(); + chunk.back -= 1; + if chunk.back == 0 { + self.chunks.pop_back(); } - } else { - None - } - } else { - None + Some(val) + }, + _ => None, } } pub(crate) fn pop_front(&mut self) -> Option { - if let Some(chunk) = self.chunks.front_mut() { - if chunk.front <= chunk.back { - unsafe { - let val = chunk.elems[chunk.front].assume_init(); - chunk.elems[chunk.front] = MaybeUninit::uninit().assume_init(); - chunk.front += 1; - if chunk.front == SIZE { - self.chunks.pop_front(); - } - Some(val) + match self.chunks.front_mut() { + Some(chunk) if chunk.front <= chunk.back => unsafe { + let val = chunk.elems[chunk.front].assume_init(); + chunk.elems[chunk.front] = MaybeUninit::uninit().assume_init(); + chunk.front += 1; + if chunk.front == SIZE { + self.chunks.pop_front(); } - } else { - None - } - } else { - None + Some(val) + }, + _ => None, } } pub(crate) fn index_front(&mut self) -> Cursor<'_, Elem, SIZE> {