packed linked list

This commit is contained in:
nora 2021-08-12 19:49:25 +02:00
parent bdc9a79368
commit 7249de2bd4
4 changed files with 336 additions and 8 deletions

View file

@ -10,5 +10,5 @@ edition = "2018"
criterion = "0.3.5" criterion = "0.3.5"
[[bench]] [[bench]]
name = "linked_list" name = "packed_linked_list"
harness = false harness = false

View file

@ -0,0 +1,63 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use datastructures::linked_list::LinkedList;
use datastructures::packed_linked_list::PackedLinkedList;
fn create_random_list(size: usize) -> LinkedList<i32> {
let mut number = 837582573;
let mut list = LinkedList::new();
for _ in 0..size {
// just random stuff I cam up with, does not need to be actually random
number = (number ^ (number << 5)) >> 3;
list.push_back(number);
}
list
}
fn create_random_packed_list_16(size: usize) -> PackedLinkedList<i32, 16> {
let mut number = 837582573;
let mut list = PackedLinkedList::new();
for _ in 0..size {
// just random stuff I cam up with, does not need to be actually random
number = (number ^ (number << 5)) >> 3;
list.push_back(number);
}
list
}
fn create_random_packed_list_128(size: usize) -> PackedLinkedList<i32, 128> {
let mut number = 837582573;
let mut list = PackedLinkedList::new();
for _ in 0..size {
// just random stuff I cam up with, does not need to be actually random
number = (number ^ (number << 5)) >> 3;
list.push_back(number);
}
list
}
fn push_back(c: &mut Criterion) {
let mut group = c.benchmark_group("push_back");
for i in [100, 1_0000_00].iter() {
group.bench_with_input(BenchmarkId::new("create_random_list", i), i, |b, i| {
b.iter(|| create_random_list(*i))
});
group.bench_with_input(
BenchmarkId::new("create_random_packed_list_16", i),
i,
|b, i| b.iter(|| create_random_packed_list_16(*i)),
);
group.bench_with_input(
BenchmarkId::new("create_random_packed_list_128", i),
i,
|b, i| b.iter(|| create_random_packed_list_128(*i)),
);
}
group.finish();
}
criterion_group!(
name = benches;
config = Criterion::default();
targets = push_back
);
criterion_main!(benches);

View file

@ -1,14 +1,15 @@
#[cfg(test)] #[cfg(test)]
mod test; mod test;
use std::iter::FromIterator;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::ptr::NonNull; use std::ptr::NonNull;
fn allocate_nonnull<T>(element: T) -> NonNull<T> { fn allocate_nonnull<T>(element: T) -> NonNull<T> {
let boxed = Box::new(element);
// SAFETY: box is always non-null // SAFETY: box is always non-null
unsafe { NonNull::new_unchecked(Box::leak(boxed)) } unsafe { NonNull::new_unchecked(Box::leak(Box::new(element))) }
} }
/// ///
@ -27,6 +28,16 @@ pub struct PackedLinkedList<T, const COUNT: usize> {
_maker: PhantomData<T>, _maker: PhantomData<T>,
} }
impl<T, const COUNT: usize> Drop for PackedLinkedList<T, COUNT> {
fn drop(&mut self) {
let mut item = self.first;
while let Some(node) = item {
let boxed = unsafe { Box::from_raw(node.as_ptr()) };
item = boxed.next;
}
}
}
impl<T, const COUNT: usize> PackedLinkedList<T, COUNT> { impl<T, const COUNT: usize> PackedLinkedList<T, COUNT> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -36,6 +47,7 @@ impl<T, const COUNT: usize> PackedLinkedList<T, COUNT> {
} }
} }
/// Pushes a new value to the front of the list
pub fn push_front(&mut self, element: T) { pub fn push_front(&mut self, element: T) {
// SAFETY: All pointers should always point to valid memory, // SAFETY: All pointers should always point to valid memory,
unsafe { unsafe {
@ -53,19 +65,143 @@ impl<T, const COUNT: usize> PackedLinkedList<T, COUNT> {
} }
} }
/// Pushes a new value to the back of the list
pub fn push_back(&mut self, element: T) {
// SAFETY: All pointers should always point to valid memory,
unsafe {
match self.last {
None => {
self.insert_node_end();
self.last.unwrap().as_mut().push_back(element)
}
Some(node) if node.as_ref().is_full() => {
self.insert_node_end();
self.last.unwrap().as_mut().push_front(element)
}
Some(mut node) => node.as_mut().push_back(element),
}
}
}
/// Pops the front element and returns it
pub fn pop_front(&mut self) -> Option<T> {
let first = &mut self.first?;
unsafe {
let node = first.as_mut();
debug_assert_ne!(node.size, 0);
let item = mem::replace(&mut node.values[0], MaybeUninit::uninit()).assume_init();
if node.size == 1 {
// the last item, deallocate it
let mut boxed = Box::from_raw(first.as_ptr());
boxed.next.as_mut().map(|next| next.as_mut().prev = None);
self.first = boxed.next;
if let None = self.first {
// if this node was the last one, also remove it from the tail pointer
self.last = None;
}
} else {
// more items, move them down
std::ptr::copy(
&node.values[1] as *const _,
&mut node.values[0] as *mut _,
node.size,
);
node.size -= 1;
}
Some(item)
}
}
/// Pops the back value and returns it
pub fn pop_back(&mut self) -> Option<T> {
let last = &mut self.last?;
unsafe {
let node = last.as_mut();
debug_assert_ne!(node.size, 0);
let item =
mem::replace(&mut node.values[node.size - 1], MaybeUninit::uninit()).assume_init();
if node.size == 1 {
// the last item, deallocate it
let mut boxed = Box::from_raw(last.as_ptr());
boxed
.prev
.as_mut()
.map(|previous| previous.as_mut().next = None);
self.last = boxed.prev;
if let None = self.last {
// if this node was the last one, also remove it from the tail pointer
self.first = None;
}
} else {
// more items
node.size -= 1;
}
Some(item)
}
}
pub fn iter(&self) -> Iter<T, COUNT> { pub fn iter(&self) -> Iter<T, COUNT> {
Iter::new(self) Iter::new(self)
} }
pub fn into_iter(self) -> IntoIter<T, COUNT> {
IntoIter::new(self)
}
fn insert_node_start(&mut self) { fn insert_node_start(&mut self) {
let node = Some(allocate_nonnull(Node::new(None, self.first))); let node = Some(allocate_nonnull(Node::new(None, self.first)));
self.first self.first
.as_mut() .as_mut()
.map(|first| unsafe { first.as_mut().prev = node }); .map(|first| unsafe { first.as_mut().prev = node });
self.first = node; self.first = node;
if let None = self.last {
self.last = node;
}
}
fn insert_node_end(&mut self) {
let node = Some(allocate_nonnull(Node::new(self.last, None)));
self.last
.as_mut()
.map(|last| unsafe { last.as_mut().next = node });
self.last = node;
if let None = self.first {
self.first = node;
}
} }
} }
impl<T, const COUNT: usize> FromIterator<T> for PackedLinkedList<T, COUNT> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut list = PackedLinkedList::new();
let mut iter = iter.into_iter();
while let Some(item) = iter.next() {
list.push_back(item);
}
list
}
}
impl<T, const COUNT: usize> Extend<T> for PackedLinkedList<T, COUNT> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
let mut iter = iter.into_iter();
while let Some(item) = iter.next() {
self.push_back(item);
}
}
}
/// A single node in the packed linked list
///
/// The node can have 1 to `COUNT` items.
/// A node is never guaranteed to be full, even if it has a next node
/// A node is always guaranteed to be non-empty
#[derive(Debug)] #[derive(Debug)]
struct Node<T, const COUNT: usize> { struct Node<T, const COUNT: usize> {
prev: Option<NonNull<Node<T, COUNT>>>, prev: Option<NonNull<Node<T, COUNT>>>,
@ -86,6 +222,7 @@ impl<T, const COUNT: usize> Node<T, COUNT> {
} }
} }
/// Checks whether the node is full
fn is_full(&self) -> bool { fn is_full(&self) -> bool {
self.size == COUNT self.size == COUNT
} }
@ -105,18 +242,20 @@ impl<T, const COUNT: usize> Node<T, COUNT> {
unsafe fn push_front(&mut self, element: T) { unsafe fn push_front(&mut self, element: T) {
debug_assert!(self.size < COUNT); debug_assert!(self.size < COUNT);
// copy all values up // copy all values up
unsafe { if COUNT > 1 {
std::ptr::copy( std::ptr::copy(
&self.values[0] as *const _, &self.values[0] as *const _,
&mut self.values[1] as *mut _, &mut self.values[1] as *mut _,
self.size, self.size,
) );
} }
self.values[0] = MaybeUninit::new(element); self.values[0] = MaybeUninit::new(element);
self.size += 1; self.size += 1;
} }
} }
#[derive(Debug)]
pub struct Iter<'a, T, const COUNT: usize> { pub struct Iter<'a, T, const COUNT: usize> {
node: Option<&'a Node<T, COUNT>>, node: Option<&'a Node<T, COUNT>>,
index: usize, index: usize,
@ -156,3 +295,70 @@ impl<'a, T, const COUNT: usize> Iterator for Iter<'a, T, COUNT> {
} }
} }
} }
#[derive(Debug)]
pub struct IntoIter<T, const COUNT: usize> {
node: Option<Box<Node<T, COUNT>>>,
index: usize,
}
impl<T, const COUNT: usize> Drop for IntoIter<T, COUNT> {
fn drop(&mut self) {
while let Some(_) = self.next() {}
}
}
impl<T, const COUNT: usize> IntoIter<T, COUNT> {
fn new(list: PackedLinkedList<T, COUNT>) -> Self {
let iter = Self {
node: list.first.map(|nn| unsafe { Box::from_raw(nn.as_ptr()) }),
index: 0,
};
// do not drop the list
mem::forget(list);
iter
}
}
impl<T, const COUNT: usize> Iterator for IntoIter<T, COUNT> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
// take the node. the node has to either be returned or replaced by a new one. the None left
// behind here is *not* a valid state
let mut node = self.node.take()?;
// SAFETY: see more detailed comments
unsafe {
if node.size > self.index {
// take more items from the node
// take out the item and replace it with uninitialized memory
// the index pointer is increased, so no one will access this again
let item =
mem::replace(&mut node.values[self.index], MaybeUninit::uninit()).assume_init();
self.index += 1;
// re-insert the node
self.node = Some(node);
Some(item)
} else {
// go to the next node
// if next is empty, return None and stop the iteration
// take ownership over the node. the last node will be dropped here
let mut next_node = Box::from_raw(node.next?.as_ptr());
next_node.prev = None;
self.index = 1;
// a node should never be empty
debug_assert_ne!(next_node.size, 0);
self.node = Some(next_node);
// see comment above
Some(
mem::replace(
&mut self.node.as_mut().unwrap().values[0],
MaybeUninit::uninit(),
)
.assume_init(),
)
}
}
}
}

View file

@ -10,6 +10,32 @@ fn push_front_single_node() {
let mut list = PackedLinkedList::<_, 16>::new(); let mut list = PackedLinkedList::<_, 16>::new();
list.push_front("hallo"); list.push_front("hallo");
} }
#[test]
fn push_front_multiple_nodes_count_1() {
let mut list = PackedLinkedList::<_, 1>::new();
list.push_front("3");
list.push_front("2");
list.push_front("1");
}
#[test]
fn push_front_multiple_nodes_count_2() {
let mut list = PackedLinkedList::<_, 2>::new();
list.push_front("3");
list.push_front("2");
list.push_front("1");
}
#[test]
fn pop_front() {
let mut list = create_sized_list::<_, 2>(&[1, 2, 3, 4]);
assert_eq!(list.pop_front(), Some(1));
assert_eq!(list.pop_front(), Some(2));
assert_eq!(list.pop_front(), Some(3));
assert_eq!(list.pop_front(), Some(4));
assert_eq!(list.pop_front(), None);
assert_eq!(list.pop_front(), None);
}
#[test] #[test]
fn iter_single_node() { fn iter_single_node() {
@ -22,7 +48,40 @@ fn iter_single_node() {
assert_eq!(iter.next(), None); assert_eq!(iter.next(), None);
} }
fn create_list<T: Clone, const COUNT: usize>(iter: &[T]) -> PackedLinkedList<T, COUNT> { #[test]
//iter.into_iter().cloned().collect() fn into_iter() {
todo!() let mut iter = create_list(&[1, 2, 3]).into_iter();
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), None);
}
// ignore this test for now
#[test]
#[cfg(all(test, not(test)))]
fn iter_mut() {
let mut list = create_list(&[1, 2, 3, 4]);
let mut iter_mut = list.iter_mut();
*iter_mut.next().unwrap() = 10;
assert!([10, 2, 3, 4].iter().zip(list.iter()).all(|(a, b)| a == b));
}
#[test]
fn from_iter() {
let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
let list = vec
.clone()
.into_iter()
.collect::<PackedLinkedList<_, 1024>>();
let list_iter = list.iter();
assert!(list_iter.zip(vec.iter()).all(|(a, b)| a == b));
}
fn create_list<T: Clone>(iter: &[T]) -> PackedLinkedList<T, 16> {
iter.into_iter().cloned().collect()
}
fn create_sized_list<T: Clone, const COUNT: usize>(iter: &[T]) -> PackedLinkedList<T, COUNT> {
iter.into_iter().cloned().collect()
} }