diff --git a/README.md b/README.md index 82a97ed..1cb9d4d 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ sudo cargo flamegraph --no-default-features --test tests -- {test_name} - Michael-Scott queue ### Linked List -- TODO: implement Harris linked list +- Harris List ### AVL Tree - SeqLockAVLTree, RwLockAVLTree(use crossbeam_utils::sync::ShardedLock) @@ -76,6 +76,9 @@ sudo cargo flamegraph --no-default-features --test tests -- {test_name} ### Queue - two lock queue, Michael-Scott Queue: https://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf +### List +- Harris List: https://www.cl.cam.ac.uk/research/srg/netos/papers/2001-caslists.pdf + ### Binary Search Tree - AVL Tree: https://stanford-ppl.github.io/website/papers/ppopp207-bronson.pdf - B+ Tree: http://www.vldb.org/pvldb/vol4/p795-sewall.pdf diff --git a/src/linkedlist/harris.rs b/src/linkedlist/harris.rs new file mode 100644 index 0000000..e6e15fa --- /dev/null +++ b/src/linkedlist/harris.rs @@ -0,0 +1,174 @@ +use std::{mem::MaybeUninit, ptr, sync::atomic::Ordering}; + +use crossbeam_epoch::{pin, Atomic, Guard, Owned, Shared}; + +use crate::map::ConcurrentMap; + +struct Node { + key: MaybeUninit, + value: MaybeUninit, + next: Atomic>, +} + +impl Node { + fn uninit() -> Self { + Self { + key: MaybeUninit::uninit(), + value: MaybeUninit::uninit(), + next: Atomic::null(), + } + } + + fn new(key: K, value: V) -> Self { + Self { + key: MaybeUninit::new(key), + value: MaybeUninit::new(value), + next: Atomic::null(), + } + } +} + +struct HarrisList { + head: Atomic>, +} + +impl HarrisList +where + K: Eq + PartialOrd, +{ + /// search left.key <= key < right.key and return (left ptr, right) + fn search<'g>( + &'g self, + key: &K, + guard: &'g Guard, + ) -> (&'g Atomic>, Shared>) { + unsafe { + let mut left = &self.head; + + loop { + let left_ref = left.load(Ordering::Acquire, guard).deref(); + let right_ref = left_ref.next.load(Ordering::Acquire, guard); + + if left_ref.key.assume_init_ref() <= key { + if right_ref.is_null() || (key < right_ref.deref().key.assume_init_ref()) { + return (left, right_ref); + } + } + + left = &left_ref.next; + } + } + } +} + +impl ConcurrentMap for HarrisList +where + K: Eq + PartialOrd + Clone, +{ + fn new() -> Self { + Self { + head: Atomic::new(Node::uninit()), // dummy node + } + } + + fn insert(&self, key: &K, value: V) -> Result<(), V> { + let guard = pin(); + + let mut node = Owned::new(Node::new(key.clone(), value)); + + unsafe { + loop { + let (left, right) = self.search(key, &guard); + + let left_ref = left.load(Ordering::Relaxed, &guard); + + if left_ref.deref().key.assume_init_ref() == key { + return Err(ptr::read(&node.value).assume_init()); + } + + node.next.store(right, Ordering::Relaxed); + + match left.compare_exchange( + left_ref, + node, + Ordering::Release, + Ordering::Relaxed, + &guard, + ) { + Ok(_) => return Ok(()), + Err(e) => node = e.new, + } + } + } + } + + fn lookup(&self, key: &K, f: F) -> R + where + F: FnOnce(Option<&V>) -> R, + { + let guard = pin(); + + unsafe { + let (left, _) = self.search(key, &guard); + let left_ref = left.load(Ordering::Relaxed, &guard).deref(); + + let value = if key == left_ref.key.assume_init_ref() { + Some(left_ref.value.assume_init_ref()) + } else { + None + }; + + f(value) + } + } + + fn get(&self, key: &K) -> Option + where + V: Clone, + { + let guard = pin(); + + unsafe { + let (left, _) = self.search(key, &guard); + let left_ref = left.load(Ordering::Relaxed, &guard).deref(); + + if key == left_ref.key.assume_init_ref() { + Some(left_ref.value.assume_init_ref().clone()) + } else { + None + } + } + } + + fn remove(&self, key: &K) -> Result { + let guard = pin(); + + unsafe { + loop { + let (left, right) = self.search(key, &guard); + + let left_ref = left.load(Ordering::Relaxed, &guard); + + if left_ref.deref().key.assume_init_ref() != key { + return Err(()); + } + + if left + .compare_exchange( + left_ref, + right, + Ordering::Relaxed, + Ordering::Relaxed, + &guard, + ) + .is_ok() + { + let value = ptr::read(&left_ref.deref().value).assume_init(); + guard.defer_destroy(left_ref); + + return Ok(value); + } + } + } + } +} diff --git a/src/linkedlist/mod.rs b/src/linkedlist/mod.rs index 7fd37e0..45195fe 100644 --- a/src/linkedlist/mod.rs +++ b/src/linkedlist/mod.rs @@ -1,9 +1,8 @@ -use crate::map::SequentialMap; +mod harris; -// simple sequential linked list -pub struct LinkedList { - head: Node, // dummy node with key = Default, but the key is not considered on algorithm -} +use std::{fmt::Debug, mem}; + +use crate::map::SequentialMap; struct Node { key: K, @@ -11,6 +10,16 @@ struct Node { next: Option>>, } +impl Debug for Node { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Node") + .field("key", &self.key) + .field("value", &self.value) + .field("next", &self.next) + .finish() + } +} + impl Default for Node { fn default() -> Self { Self::new(K::default(), V::default()) @@ -27,35 +36,70 @@ impl Node { } } -impl SequentialMap for LinkedList +// simple sequential sorted linked list +pub struct SortedList { + head: Node, // dummy node with key = Default, but the key is not considered on algorithm +} + +impl Debug for SortedList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SortedList") + .field("head", &self.head) + .finish() + } +} + +impl SortedList { + pub fn keys(&self) -> Vec<&K> { + let mut result = Vec::new(); + + let mut node = &self.head.next; + + while let Some(inner) = node { + result.push(&inner.key); + node = &inner.next; + } + + result + } +} + +impl SequentialMap for SortedList where - K: Default + Eq + Clone, + K: Default + Eq + PartialOrd + Clone, V: Default, { - fn new() -> LinkedList { - LinkedList { + fn new() -> SortedList { + SortedList { head: Node::default(), } } fn insert(&mut self, key: &K, value: V) -> Result<(), V> { - let new = Box::new(Node::new(key.clone(), value)); + let mut new = Box::new(Node::new(key.clone(), value)); - let mut current = &mut self.head.next; + let mut current = &mut self.head; loop { - match current { - Some(node) => { - if node.key == *key { - return Err(new.value); - } + let next = &mut current.next; + + if next.is_some() { + let next_key = &next.as_ref().unwrap().key; + + if next_key == key { + return Err(new.value); + } else if next_key > key { + // insert for sorted keys + mem::swap(next, &mut new.next); + let _ = mem::replace(next, Some(new)); - current = &mut node.next; - } - None => { - *current = Some(new); return Ok(()); } + + current = next.as_mut().unwrap(); + } else { + *next = Some(new); + return Ok(()); } } } @@ -100,7 +144,7 @@ where } } -impl Drop for LinkedList { +impl Drop for SortedList { fn drop(&mut self) { let mut node = self.head.next.take(); diff --git a/tests/linkedlist/linkedlist.rs b/tests/linkedlist/linkedlist.rs index 1bf521d..0505fab 100644 --- a/tests/linkedlist/linkedlist.rs +++ b/tests/linkedlist/linkedlist.rs @@ -1,10 +1,11 @@ use crate::util::map::stress_sequential; -use cds::linkedlist::LinkedList; +use cds::linkedlist::SortedList; use cds::map::SequentialMap; +use rand::{thread_rng, Rng}; #[test] -fn test_linkedlist() { - let mut list: LinkedList = LinkedList::new(); +fn test_sorted_list() { + let mut list: SortedList = SortedList::new(); assert_eq!(list.lookup(&1), None); @@ -40,6 +41,24 @@ fn test_linkedlist() { } #[test] -fn stress_linkedlist() { - stress_sequential::>(100_000); +fn test_sorted_list_is_sorted() { + let mut list: SortedList = SortedList::new(); + + let mut rng = thread_rng(); + + for _ in 0..100 { + let val = rng.gen_range(0..39393939); + assert_eq!(list.insert(&val, val), Ok(())); + } + + list.keys() + .windows(2) + .enumerate() + .map(|(i, n)| assert!(n[0] < n[1], "{} < {} on {}", n[0], n[1], i)) + .nth(98); +} + +#[test] +fn stress_sorted_list() { + stress_sequential::>(100_000); }