diff --git a/rust/src/daba/chunked_array_queue.rs b/rust/src/daba/chunked_array_queue.rs new file mode 100644 index 0000000..74bbb57 --- /dev/null +++ b/rust/src/daba/chunked_array_queue.rs @@ -0,0 +1,182 @@ +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 { + 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(); + } + Some(val) + }, + _ => None, + } + } + pub(crate) fn pop_front(&mut self) -> Option { + 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(); + } + Some(val) + }, + _ => 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 new file mode 100644 index 0000000..098fa05 --- /dev/null +++ b/rust/src/daba/mod.rs @@ -0,0 +1,162 @@ +mod chunked_array_queue; +use crate::FifoWindow; +use alga::general::AbstractMonoid; +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 + Value: AbstractMonoid + Clone, + BinOp: Operator, +{ + // ith oldest value in FIFO order stored at vi = vals[i] + 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, +} + +impl FifoWindow for DABA +where + Value: AbstractMonoid + Clone, + BinOp: Operator, +{ + fn new() -> Self { + Self { + items: VecDeque::new(), + l: 0, + r: 0, + a: 0, + b: 0, + op: PhantomData, + } + } + fn push(&mut self, v: Value) { + let agg = self.agg_b().operate(&v); + self.items.push_back(Item::new(v, agg)); + self.fixup(); + } + fn pop(&mut self) { + if self.items.pop_front().is_some() { + 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.items.len() + } + fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +impl DABA +where + Value: AbstractMonoid + Clone, + BinOp: Operator, +{ + #[inline(always)] + fn agg_f(&self) -> Value { + if self.items.is_empty() { + Value::identity() + } else { + self.items.front().unwrap().agg.clone() + } + } + #[inline(always)] + fn agg_b(&self) -> Value { + if self.b == self.items.len() { + Value::identity() + } else { + self.items.back().unwrap().agg.clone() + } + } + #[inline(always)] + fn agg_l(&self) -> Value { + if self.l == self.r { + Value::identity() + } else { + self.items[self.l].agg.clone() + } + } + #[inline(always)] + fn agg_r(&self) -> Value { + if self.r == self.a { + Value::identity() + } else { + self.items[self.a - 1].agg.clone() + } + } + #[inline(always)] + fn agg_a(&self) -> Value { + if self.a == self.b { + Value::identity() + } else { + self.items[self.a].agg.clone() + } + } + #[inline(always)] + 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.items.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.items.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.items[self.l].agg = self.agg_l().operate(&self.agg_r()).operate(&self.agg_a()); + self.l += 1; + self.items[self.a - 1].agg = self.items[self.a - 1].val.operate(&self.agg_a()); + self.a -= 1; + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4db68e3..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; @@ -57,3 +62,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 ] }