diff --git a/concurrent-btree/.gitignore b/concurrent-btree/.gitignore index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..088ba6ba7d345b76aa2b8dc021dd25e1323189b3 100644 --- a/concurrent-btree/.gitignore +++ b/concurrent-btree/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/concurrent-btree/Cargo.toml b/concurrent-btree/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..02f4e0c044336b7165f5b60a62f19a4efd70e43f --- /dev/null +++ b/concurrent-btree/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "btree" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4" +parking_lot = "0.12" +criterion = "0.4" + +[[bench]] +name = "btree_benchmark" +harness = false \ No newline at end of file diff --git a/concurrent-btree/LICENSE b/concurrent-btree/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ab30ee0c0cf3ef79a19029ec9c16f2265d450e15 --- /dev/null +++ b/concurrent-btree/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 datenlord + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/concurrent-btree/README.md b/concurrent-btree/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6f1a10eeb22eaeca3ad28e610ae0706cf60ad6b9 --- /dev/null +++ b/concurrent-btree/README.md @@ -0,0 +1,2 @@ +# btree +high-performance & lock-free b-tree diff --git a/concurrent-btree/benches/btree_benchmark.rs b/concurrent-btree/benches/btree_benchmark.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2fac79f9d4d6c3395e4df3d50b10381c2ef62b1 --- /dev/null +++ b/concurrent-btree/benches/btree_benchmark.rs @@ -0,0 +1,221 @@ +use btree::BLinkTree; +use criterion::{criterion_group, criterion_main, Criterion}; +use parking_lot::RwLock; +use std::collections::BTreeMap; +use std::sync::Arc; +use std::thread; + +static THREAD_NUM: i32 = 20; + +fn bench_btree_map(c: &mut Criterion) { + let mut group = c.benchmark_group("BTreeMap"); + let b_tree_map = Arc::new(RwLock::new(BTreeMap::new())); + + // use 10 threads to insert, only bench 1 thread + let mut handles = vec![]; + for thread_i in 1..THREAD_NUM { + let b_tree_map_arc = Arc::clone(&b_tree_map); + let handle = thread::spawn(move || { + // insert + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_tree_map_arc.write().insert(i, i); + } + }); + handles.push(handle); + } + // bench insert + group.bench_function("Insert", |b| { + b.iter(|| { + let thread_i = THREAD_NUM; + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_tree_map.write().insert(i, i); + } + }) + }); + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + + // use 10 threads to get, only bench 1 thread + handles = vec![]; + for thread_i in 1..THREAD_NUM { + let b_tree_map_arc = Arc::clone(&b_tree_map); + let handle = thread::spawn(move || { + // insert + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_tree_map_arc.read().get(&i).unwrap(); + } + }); + handles.push(handle); + } + // bench get + group.bench_function("Get", |b| { + b.iter(|| { + let thread_i = THREAD_NUM; + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_tree_map.read().get(&i).unwrap(); + } + }) + }); + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + + // use 10 threads to remove, only bench 1 thread + handles = vec![]; + for thread_i in 1..THREAD_NUM { + let b_tree_map_arc = Arc::clone(&b_tree_map); + let handle = thread::spawn(move || { + // insert + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_tree_map_arc.write().remove(&i); + } + }); + handles.push(handle); + } + // bench remove + group.bench_function("Remove", |b| { + b.iter(|| { + let thread_i = THREAD_NUM; + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_tree_map.write().remove(&i); + } + }) + }); + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + + group.finish(); +} + +fn bench_blink_tree(c: &mut Criterion) { + let mut group = c.benchmark_group("BLinkTree"); + let b_link_tree = Arc::new(BLinkTree::new( + 6, + (i32::MIN, i32::MIN), + (i32::MAX, i32::MAX), + )); + + // use 10 threads to insert, only bench 1 thread + let mut handles = vec![]; + for thread_i in 1..THREAD_NUM { + let b_link_tree_arc = Arc::clone(&b_link_tree); + let handle = thread::spawn(move || { + // insert + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_link_tree_arc.insert(i, i); + } + }); + handles.push(handle); + } + // bench insert + group.bench_function("Insert", |b| { + b.iter(|| { + let thread_i = THREAD_NUM; + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_link_tree.insert(i, i); + } + }) + }); + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + + // use 10 threads to get, only bench 1 thread + handles = vec![]; + for thread_i in 1..THREAD_NUM { + let b_link_tree_arc = Arc::clone(&b_link_tree); + let handle = thread::spawn(move || { + // insert + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_link_tree_arc.get(&i).unwrap(); + } + }); + handles.push(handle); + } + // bench get + group.bench_function("Get", |b| { + b.iter(|| { + let thread_i = THREAD_NUM; + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_link_tree.get(&i).unwrap(); + } + }) + }); + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + + // use 10 threads to remove, only bench 1 thread + handles = vec![]; + for thread_i in 1..THREAD_NUM { + let b_link_tree_arc = Arc::clone(&b_link_tree); + let handle = thread::spawn(move || { + // insert + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_link_tree_arc.remove(&i); + } + }); + handles.push(handle); + } + // bench remove + group.bench_function("Remove", |b| { + b.iter(|| { + let thread_i = THREAD_NUM; + for i in 1..100001 { + if i % THREAD_NUM != THREAD_NUM - thread_i { + continue; + } + b_link_tree.remove(&i); + } + }) + }); + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + + group.finish(); +} + +criterion_group!(benches, bench_btree_map, bench_blink_tree,); +criterion_main!(benches); diff --git a/concurrent-btree/src/b_link_tree.rs b/concurrent-btree/src/b_link_tree.rs new file mode 100644 index 0000000000000000000000000000000000000000..e94d39b4fd7b9b90ccccfdc729ed3eaf6b605266 --- /dev/null +++ b/concurrent-btree/src/b_link_tree.rs @@ -0,0 +1,662 @@ +use log::warn; +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::{ + mem::swap, + ops::RangeBounds, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; + +/// An ordered map based on a [B-Link-Tree]. +#[derive(Debug)] +pub struct BLinkTree { + // each node contains at most 2 * half_maximum_entries values + half_maximum_entries: usize, + // root node of this b-tree + root: RwLock>>, + // the head(leftmost) node of leaf nodes + leaf_nodes_head: Arc>, + // number of values in this b-tree + size: AtomicUsize, +} + +/// Tree node +#[derive(Debug)] +struct Node { + // leaf node or not (never change after node has been created) + is_leaf: bool, + // contents of this node, protected by read write lock + content: Arc>>, +} + +/// Tree node contents, should be protected by read write lock +#[derive(Debug)] +struct NodeContent { + // high key, an upper bound on keys stored in the subtree with this node as the root + high_key: Arc, + // pointer to the right sibling node at the same level, null if this node is the rightmost + link_pointer: Option>>, + // keys, at most 2 * half_maximum_entries keys in one node + keys: Vec>, + // non-leaf nodes: store son nodes, length == keys.len() + 1, son node values[i]' keys are smaller than keys[i] + // leaf nodes: store values, length == keys.len() + values: Vec>, +} + +/// Value cell, stores value or node pointer +#[derive(Debug)] +enum ValueCell { + // non-leaf node stores pointers to the son nodes + Node(Arc>), + // leaf node stores values + Value(Arc), +} + +/// Search status +enum SearchResult { + // target key has its corresponding value + Found(Arc), + // target key doesn't have its corresponding value + NotFound(), + // traverse to child node + GoDown(Arc>), + // traverse to right sibling node + GoRight(Arc>), + // current data structure is broken + Error(), +} + +impl BLinkTree { + /// Makes a new `BLinkTree`, with two initial entries in it. + /// Each node contains at most 2 * half_maximum_entries entries. + /// + /// # Panics + /// + /// Panics if the first key equals the second key. + /// + /// # Examples + /// + /// ``` + /// use btree::BLinkTree; + /// + /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); + /// assert_eq!(2, tree.len()); + /// ``` + pub fn new( + half_maximum_entries: usize, + mut kv_pair1: (K, V), + mut kv_pair2: (K, V), + ) -> BLinkTree { + // make sure key 0 is less than key 1 + if kv_pair1.0 == kv_pair2.0 { + panic!("Please give two different initial keys."); + } else if kv_pair1.0 > kv_pair2.0 { + swap(&mut kv_pair1, &mut kv_pair2); + } + // construct two leaf nodes + let tail_high_key_arc = Arc::new(kv_pair2.0); + let leaf_nodes_tail = Arc::new(Node::new( + true, + Arc::clone(&tail_high_key_arc), + None, + vec![Arc::clone(&tail_high_key_arc)], + vec![ValueCell::Value(Arc::new(kv_pair2.1))], + )); + let head_high_key_arc = Arc::new(kv_pair1.0); + let leaf_nodes_head = Arc::new(Node::new( + true, + Arc::clone(&head_high_key_arc), + Some(Arc::clone(&leaf_nodes_tail)), + vec![Arc::clone(&head_high_key_arc)], + vec![ValueCell::Value(Arc::new(kv_pair1.1))], + )); + // construct tree + BLinkTree { + half_maximum_entries, + root: RwLock::new(Arc::new(Node::new( + false, + Arc::clone(&tail_high_key_arc), + None, + vec![Arc::clone(&tail_high_key_arc)], + vec![ + ValueCell::Node(Arc::clone(&leaf_nodes_head)), + ValueCell::Node(Arc::clone(&leaf_nodes_tail)), + ], + ))), + leaf_nodes_head: Arc::clone(&leaf_nodes_head), + size: AtomicUsize::new(2), + } + } + + /// Inserts a key-value pair into the map. + /// If the map did not have this key present, None is returned. + /// If the map did have this key present, the value is updated, and the old value is returned. The key is not updated, though; this matters for types that can be == without being identical. + /// + /// # Examples + /// + /// ``` + /// use btree::BLinkTree; + /// + /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); + /// assert_eq!(None, tree.insert(1, "a")); + /// assert_eq!(false, tree.is_empty()); + /// + /// tree.insert(1, "b"); + /// assert_eq!("b", *tree.insert(1, "c").unwrap()); + /// assert_eq!("c", *tree.get(&1).unwrap()); + /// ``` + pub fn insert(&self, key: K, value: V) -> Option> { + let mut k = Arc::new(key); + let mut path_stack = Vec::new(); + // init old node and current node + let root_rg = self.root.read(); + let mut current_node = Arc::clone(&root_rg); + drop(root_rg); + let mut old_node; + // find from non-leaf node to leaf node + while !current_node.is_leaf { + old_node = Arc::clone(¤t_node); + let (search_result, old_node_rg) = old_node.scan_node_rg(&k); + match search_result { + SearchResult::GoDown(next) => { + path_stack.push(Arc::clone(&old_node)); + current_node = Arc::clone(&next); + if *old_node_rg.high_key < *k { + drop(old_node_rg); + let mut old_node_wg = old_node.content.write(); + old_node_wg.high_key = Arc::clone(&k); + } + } + SearchResult::GoRight(next) => current_node = Arc::clone(&next), + _ => { + warn!("Fail to find the leaf node. Skip this insert operation"); + return None; + } + } + } + + let mut v: ValueCell = ValueCell::Value(Arc::new(value)); + let mut current_node_content_arc = Arc::clone(¤t_node.content); + let mut current_node_content_ptr = Arc::as_ptr(¤t_node_content_arc); + let mut current_node_wg = unsafe { (*current_node_content_ptr).write() }; + // move right + loop { + let search_result = current_node.scan_node_wg(&k, ¤t_node_wg); + if let SearchResult::GoRight(next) = search_result { + // first get next node's guard, then drop current node's guard + current_node = next; + current_node_content_arc = Arc::clone(¤t_node.content); + current_node_content_ptr = Arc::as_ptr(¤t_node_content_arc); + let old_node_wg = current_node_wg; + // SAFETY: current_node_wg borrows current_node_content_arc, drop old_node_wg before previous current_node_content_arc is dropped + current_node_wg = unsafe { (*current_node_content_ptr).write() }; + drop(old_node_wg); + } else { + break; + } + } + + loop { + // replace or insert value + let insert_pos = current_node_wg.keys.binary_search(&k); + if let Ok(pos) = insert_pos { + current_node_wg.values.insert(pos, v); + return if let ValueCell::Value(value) = current_node_wg.values.remove(pos + 1) { + Some(value) + } else { + warn!("Add duplicate key in the non-leaf nodes. Stop backtracking caused by insert operation."); + None + }; + } else if let Err(pos) = insert_pos { + if k > current_node_wg.high_key { + current_node_wg.high_key = k.clone(); + } + current_node_wg.keys.insert(pos, k); + match v { + // child node should at the right position because all keys in the child node are bigger than k + ValueCell::Node(_) => current_node_wg.values.insert(pos + 1, v), + // value should at the same position + ValueCell::Value(_) => current_node_wg.values.insert(pos, v), + } + } + + // propagating splits + if current_node_wg.keys.len() < self.half_maximum_entries * 2 { + drop(current_node_wg); + break; + } else { + // rearrange + let new_node_keys = current_node_wg.keys.split_off(self.half_maximum_entries); + let new_node_values = current_node_wg.values.split_off(self.half_maximum_entries); + let new_node_high_key = current_node_wg.high_key.clone(); + let new_node_link_pointer; + match ¤t_node_wg.link_pointer { + Some(next) => new_node_link_pointer = Some(Arc::clone(next)), + None => new_node_link_pointer = None, + } + if current_node.is_leaf { + current_node_wg.high_key = current_node_wg.keys.last().unwrap().clone(); + } else { + // non-leaf node's last key will become high key + current_node_wg.high_key = current_node_wg.keys.pop().unwrap(); + } + // link current node and new node + let new_node = Arc::new(Node::new( + current_node.is_leaf, + new_node_high_key, + new_node_link_pointer, + new_node_keys, + new_node_values, + )); + current_node_wg.link_pointer = Some(Arc::clone(&new_node)); + // all keys in the new node are bigger than new k + k = current_node_wg.keys.last().unwrap().clone(); + v = ValueCell::Node(Arc::clone(&new_node)); + // find parent node to insert + let old_node_wg; + if let Some(node) = path_stack.pop() { + current_node = node; + // first get next node's guard, then drop current node's guard + current_node_content_arc = Arc::clone(¤t_node.content); + current_node_content_ptr = Arc::as_ptr(¤t_node_content_arc); + old_node_wg = current_node_wg; + current_node_wg = unsafe { (*current_node_content_ptr).write() }; + } else { + break; + } + // move right + loop { + let search_result = current_node.scan_node_wg(&k, ¤t_node_wg); + if let SearchResult::GoRight(next) = search_result { + // first get next node's guard, then drop current node's guard + current_node = next; + current_node_content_arc = Arc::clone(¤t_node.content); + current_node_content_ptr = Arc::as_ptr(¤t_node_content_arc); + let old_node_wg = current_node_wg; + // SAFETY: current_node_wg borrows current_node_content_arc, drop old_node_wg before previous current_node_content_arc is dropped + current_node_wg = unsafe { (*current_node_content_ptr).write() }; + drop(old_node_wg); + } else { + break; + } + } + drop(old_node_wg); + } + } + + self.size.fetch_add(1, Ordering::Release); + None + } + + fn find_leaf_node(&self, key: &K) -> Option>> { + // init old node and current node + let root_rg = self.root.read(); + let mut current_node = Arc::clone(&root_rg); + drop(root_rg); + let mut old_node; + // find from non-leaf node to leaf node + while !current_node.is_leaf { + old_node = Arc::clone(¤t_node); + let (search_result, _) = old_node.scan_node_rg(key); + match search_result { + SearchResult::GoDown(next) | SearchResult::GoRight(next) => { + current_node = Arc::clone(&next) + } + _ => { + warn!("Fail to find the leaf node. Skip this operation"); + return None; + } + } + } + return Some(current_node); + } + + /// Removes a key from the map, returning the value at the key if the key was previously in the map. + /// The key may be any borrowed form of the map’s key type, but the ordering on the borrowed form must match the ordering on the key type. + /// + /// # Examples + /// + /// ``` + /// use btree::BLinkTree; + /// + /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); + /// tree.insert(1, "a"); + /// assert_eq!("a", *tree.remove(&1).unwrap()); + /// assert_eq!(None, tree.remove(&1)); + /// ``` + pub fn remove(&self, key: &K) -> Option> { + let mut current_node; + if let Some(node) = self.find_leaf_node(key) { + current_node = node; + } else { + return None; + } + // find until the leaf node contains the given key + loop { + // TODO: use read guard to search, then upgrade to write guard to delete + let mut current_node_wg = current_node.content.write(); + let search_result = current_node.scan_node_wg(key, ¤t_node_wg); + match search_result { + SearchResult::Found(_) => { + let result = current_node_wg.remove(key); + drop(current_node_wg); + return if let Some(ValueCell::Value(val)) = result { + self.size.fetch_sub(1, Ordering::Release); + Some(val) + } else if let Some(ValueCell::Node(_)) = result { + warn!("Delete node pointer."); + None + } else { + None + }; + } + SearchResult::GoDown(next) | SearchResult::GoRight(next) => { + drop(current_node_wg); + current_node = Arc::clone(&next); + } + SearchResult::NotFound() => break, + SearchResult::Error() => { + warn!("Unrecoverable error occurs."); + break; + } + } + } + None + } + + /// Returns a reference to the value corresponding to the key. + /// The key may be any borrowed form of the map’s key type, but the ordering on the borrowed form must match the ordering on the key type. + /// + /// # Examples + /// + /// ``` + /// use btree::BLinkTree; + /// + /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); + /// tree.insert(1, "a"); + /// assert_eq!("a", *tree.get(&1).unwrap()); + /// assert_eq!(None, tree.get(&2)); + /// ``` + pub fn get(&self, key: &K) -> Option> { + let mut current_node; + if let Some(node) = self.find_leaf_node(key) { + current_node = node; + } else { + return None; + } + // find until the leaf node contains the given key + loop { + let (search_result, _) = current_node.scan_node_rg(&key); + match search_result { + SearchResult::Found(value) => return Some(value), + SearchResult::GoDown(next) | SearchResult::GoRight(next) => { + current_node = Arc::clone(&next); + } + SearchResult::NotFound() => break, + SearchResult::Error() => { + warn!("Unrecoverable error occurs."); + break; + } + } + } + None + } + + /// Constructs a double-ended iterator over a sub-range of elements in the map. The simplest way is to use the range syntax min..max, thus range(min..max) will yield elements from min (inclusive) to max (exclusive). The range may also be entered as (Bound, Bound), so for example range((Excluded(4), Included(10))) will yield a left-exclusive, right-inclusive range from 4 to 10. + /// + /// # Panics + /// + /// Panics if range start > end. Panics if range start == end and both bounds are Excluded. + /// + /// # Examples + /// + /// ``` + /// use std::ops::Bound::Included; + /// use btree::BLinkTree; + /// + /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); + /// tree.insert(3, "a"); + /// tree.insert(5, "b"); + /// tree.insert(8, "c"); + /// let (keys, values) = tree.range((Included(4), Included(8))); + /// for i in 0..keys.len() { + /// println!("{:#?}: {:#?}", keys.get(i), values.get(i)); + /// } + /// ``` + pub fn range(&self, range: R) -> (Vec>, Vec) + where + T: Ord + ?Sized, + R: RangeBounds, + { + // TODO: complete range search + range.start_bound(); + let _leaf_nodes_head = Arc::clone(&self.leaf_nodes_head); + (vec![], vec![]) + } + + /// Returns the number of elements in the map. + /// + /// # Examples + /// + /// ``` + /// use btree::BLinkTree; + /// + /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); + /// assert_eq!(2, tree.len()); + /// tree.insert(1, "a"); + /// assert_eq!(3, tree.len()); + /// ``` + pub fn len(&self) -> usize { + self.size.load(Ordering::Acquire) + } + + /// Returns true if the map contains no elements. + /// + /// # Examples + /// + /// ``` + /// use btree::BLinkTree; + /// + /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); + /// assert!(!tree.is_empty()); + /// tree.remove(&i32::MIN); + /// tree.remove(&i32::MAX); + /// assert!(tree.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.size.load(Ordering::Acquire) == 0 + } +} + +impl Node { + /// Makes a new tree node. + /// Each node contains at most 2 * half_maximum_entries entries. + /// + /// # Panics + /// + /// Panics if keys length and values length violate the constraints. + /// non-leaf nodes: values.len() == keys.len() + 1 + /// leaf nodes: values.len() == keys.len() + pub(crate) fn new( + is_leaf: bool, + high_key: Arc, + link_pointer: Option>>, + keys: Vec>, + values: Vec>, + ) -> Node { + if is_leaf && values.len() != keys.len() { + panic!("for leaf nodes, values.len() should equal keys.len()."); + } else if !is_leaf && values.len() != keys.len() + 1 { + panic!("for non-leaf nodes, values.len() should equal keys.len() + 1."); + } + return Node { + is_leaf, + content: Arc::new(RwLock::new(NodeContent::new( + high_key, + link_pointer, + keys, + values, + ))), + }; + } + + /// Returns search result and this nodes' read guard. + pub(crate) fn scan_node_rg( + &self, + key: &K, + ) -> (SearchResult, RwLockReadGuard>) { + let node_rg = self.content.read(); + let key_pos = &node_rg.keys.binary_search_by(|probe| (**probe).cmp(key)); + return if self.is_leaf { + // find until position's key == key + match key_pos { + Ok(pos) => { + // value cells in leaf nodes should be value type + if let ValueCell::Value(val) = node_rg.values.get(*pos).unwrap() { + (SearchResult::Found(Arc::clone(val)), node_rg) + } else { + (SearchResult::Error(), node_rg) + } + } + Err(pos) => { + // check right sibling node + if *pos == node_rg.keys.len() && key > &node_rg.high_key { + if let Some(pointer) = &node_rg.link_pointer { + (SearchResult::GoRight(Arc::clone(&pointer)), node_rg) + } else { + (SearchResult::NotFound(), node_rg) + } + } else { + (SearchResult::NotFound(), node_rg) + } + } + } + } else { + // find until position's key <= key + match key_pos { + Ok(pos) => { + // value cells in leaf nodes should be node type + if let ValueCell::Node(next) = node_rg.values.get(*pos).unwrap() { + (SearchResult::GoDown(Arc::clone(&next)), node_rg) + } else { + (SearchResult::Error(), node_rg) + } + } + Err(pos) => { + if *pos < node_rg.keys.len() + || (*pos == node_rg.keys.len() && key <= &node_rg.high_key) + { + // value cells in leaf nodes should be node type + if let ValueCell::Node(next) = node_rg.values.get(*pos).unwrap() { + (SearchResult::GoDown(Arc::clone(&next)), node_rg) + } else { + (SearchResult::Error(), node_rg) + } + } else if key > &node_rg.high_key { + if let Some(pointer) = &node_rg.link_pointer { + (SearchResult::GoRight(Arc::clone(&pointer)), node_rg) + } else { + (SearchResult::Error(), node_rg) + } + } else { + (SearchResult::Error(), node_rg) + } + } + } + }; + } + + /// Returns search result. + pub(crate) fn scan_node_wg( + &self, + key: &K, + node_wg: &RwLockWriteGuard>, + ) -> SearchResult { + let key_pos = node_wg.keys.binary_search_by(|probe| (**probe).cmp(key)); + return if self.is_leaf { + // find until position's key == key + match key_pos { + Ok(pos) => { + // value cells in leaf nodes should be value type + if let ValueCell::Value(val) = node_wg.values.get(pos).unwrap() { + SearchResult::Found(Arc::clone(val)) + } else { + SearchResult::Error() + } + } + Err(pos) => { + if pos == node_wg.keys.len() && key > &node_wg.high_key { + if let Some(pointer) = &node_wg.link_pointer { + SearchResult::GoRight(Arc::clone(&pointer)) + } else { + SearchResult::NotFound() + } + } else { + SearchResult::NotFound() + } + } + } + } else { + // find until position's key <= key + match key_pos { + Ok(pos) => { + // value cells in leaf nodes should be node type + if let ValueCell::Node(next) = node_wg.values.get(pos).unwrap() { + SearchResult::GoDown(Arc::clone(next)) + } else { + SearchResult::Error() + } + } + Err(pos) => { + if pos < node_wg.keys.len() + || (pos == node_wg.keys.len() && key <= &node_wg.high_key) + { + // value cells in leaf nodes should be node type + if let ValueCell::Node(next) = node_wg.values.get(pos).unwrap() { + SearchResult::GoDown(Arc::clone(&next)) + } else { + SearchResult::Error() + } + } else if key > &node_wg.high_key { + if let Some(pointer) = &node_wg.link_pointer { + SearchResult::GoRight(Arc::clone(&pointer)) + } else { + SearchResult::Error() + } + } else { + SearchResult::Error() + } + } + } + }; + } +} + +impl NodeContent { + pub(crate) fn new( + high_key: Arc, + link_pointer: Option>>, + keys: Vec>, + values: Vec>, + ) -> NodeContent { + return NodeContent { + high_key, + link_pointer, + keys, + values, + }; + } + + pub(crate) fn remove(&mut self, key: &K) -> Option> { + let key_pos = self.keys.binary_search_by(|probe| (**probe).cmp(key)); + return match key_pos { + Ok(pos) => { + self.keys.remove(pos); + Some(self.values.remove(pos)) + } + Err(_) => None, + }; + } +} diff --git a/concurrent-btree/src/lib.rs b/concurrent-btree/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d451ecf60f1946607b801fd96746e4f1832c01d4 --- /dev/null +++ b/concurrent-btree/src/lib.rs @@ -0,0 +1,112 @@ +#![deny( + // The following are allowed by default lints according to + // https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + absolute_paths_not_starting_with_crate, + // box_pointers, async trait must use it + // elided_lifetimes_in_paths, // allow anonymous lifetime3 months ago • Zheng Pan [Add storage module and support single node pu…] + explicit_outlives_requirements, + keyword_idents, + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + // must_not_suspend, unstable + non_ascii_idents, + // non_exhaustive_omitted_patterns, unstable + noop_method_call, + pointer_structural_match, + rust_2021_incompatible_closure_captures, + rust_2021_incompatible_or_patterns, + rust_2021_prefixes_incompatible_syntax, + rust_2021_prelude_collisions, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + // unsafe_code, + unsafe_op_in_unsafe_fn, + unstable_features, + // unused_crate_dependencies, the false positive case blocks us + unused_extern_crates, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + // unused_results, + variant_size_differences, + warnings, // treat all wanings as errors + clippy::all, + clippy::pedantic, + clippy::cargo, + // The followings are selected restriction lints for rust 1.57 + clippy::as_conversions, + clippy::clone_on_ref_ptr, + clippy::create_dir, + clippy::dbg_macro, + clippy::decimal_literal_representation, + // clippy::default_numeric_fallback, too verbose when dealing with numbers + clippy::disallowed_script_idents, + clippy::else_if_without_else, + clippy::exhaustive_enums, + clippy::exhaustive_structs, + clippy::exit, + clippy::expect_used, + clippy::filetype_is_file, + clippy::float_arithmetic, + clippy::float_cmp_const, + clippy::get_unwrap, + clippy::if_then_some_else_none, + // clippy::implicit_return, it's idiomatic Rust code. + clippy::indexing_slicing, + // clippy::inline_asm_x86_att_syntax, stick to intel syntax + clippy::inline_asm_x86_intel_syntax, + clippy::integer_arithmetic, + // clippy::integer_division, required in the project + clippy::let_underscore_must_use, + clippy::lossy_float_literal, + clippy::map_err_ignore, + clippy::mem_forget, + clippy::missing_docs_in_private_items, + clippy::missing_enforced_import_renames, + clippy::missing_inline_in_public_items, + // clippy::mod_module_files, mod.rs file is used + clippy::modulo_arithmetic, + clippy::multiple_inherent_impl, + // clippy::panic, allow in application code + // clippy::panic_in_result_fn, not necessary as panic is banned + clippy::pattern_type_mismatch, + clippy::print_stderr, + clippy::print_stdout, + clippy::rc_buffer, + clippy::rc_mutex, + clippy::rest_pat_in_fully_bound_structs, + clippy::same_name_method, + clippy::self_named_module_files, + // clippy::shadow_reuse, it’s a common pattern in Rust code + // clippy::shadow_same, it’s a common pattern in Rust code + clippy::shadow_unrelated, + clippy::str_to_string, + clippy::string_add, + clippy::string_to_string, + clippy::todo, + clippy::unimplemented, + clippy::unnecessary_self_imports, + clippy::unneeded_field_pattern, + // clippy::unreachable, allow unreachable panic, which is out of expectation + clippy::unwrap_in_result, + clippy::unwrap_used, + // clippy::use_debug, debug is allow for debug log + clippy::verbose_file_reads, + clippy::wildcard_enum_match_arm, +)] +#![allow( + clippy::panic, // allow debug_assert, panic in production code3 months ago • Zheng Pan [Add Xline server and add github CI] + clippy::multiple_crate_versions, // caused by the dependency, can't be fixed3 months ago • Jicheng Shi [Add curp steps] +)] + +//! Tree maps + +mod b_link_tree; + +pub use b_link_tree::BLinkTree; diff --git a/concurrent-btree/tests/concurrent_test.rs b/concurrent-btree/tests/concurrent_test.rs new file mode 100644 index 0000000000000000000000000000000000000000..64acbda53034d9e14b7f4c341e5d14d38e9e0d7f --- /dev/null +++ b/concurrent-btree/tests/concurrent_test.rs @@ -0,0 +1,124 @@ +use btree::BLinkTree; +use std::sync::Arc; +use std::thread; + +#[test] +fn test_concurrent_insert() { + let tree = Arc::new(BLinkTree::new( + 50, + (i32::MIN, i32::MIN), + (i32::MAX, i32::MAX), + )); + // create 10 threads + let mut handles = vec![]; + for i in 1..11 { + let thread_i = i; + let tree_arc = Arc::clone(&tree); + let handle = thread::spawn(move || { + // insert + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!(None, tree_arc.insert(i, i), "insert {} fails.", i); + } + // get + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!(i, *tree_arc.get(&i).unwrap(), "get {} fails.", i); + } + // insert and get old value + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!( + i, + *tree_arc.insert(i, 100001 - i).unwrap(), + "insert {} fails.", + i + ); + } + }); + handles.push(handle); + } + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + assert_eq!(100002, tree.len()); +} + +#[test] +fn test_concurrent_remove() { + let tree = Arc::new(BLinkTree::new( + 50, + (i32::MIN, i32::MIN), + (i32::MAX, i32::MAX), + )); + // create 10 threads + let mut handles = vec![]; + for i in 1..11 { + let thread_i = i; + let tree_arc = Arc::clone(&tree); + let handle = thread::spawn(move || { + // insert + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!(None, tree_arc.insert(i, i), "insert {} fails.", i); + } + // get + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!(i, *tree_arc.get(&i).unwrap(), "get {} fails.", i); + } + }); + handles.push(handle); + } + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + assert_eq!(100002, tree.len()); + + handles = vec![]; + for i in 1..11 { + let thread_i = i; + let tree_arc = Arc::clone(&tree); + let handle = thread::spawn(move || { + // delete + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!(i, *tree_arc.remove(&i).unwrap(), "remove {} fails.", i); + } + // insert + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!(None, tree_arc.insert(i, i), "insert {} fails.", i); + } + // get + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!(i, *tree_arc.get(&i).unwrap(), "get {} fails.", i); + } + }); + handles.push(handle); + } + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + assert_eq!(100002, tree.len()); +} diff --git a/concurrent-btree/tests/serial_test.rs b/concurrent-btree/tests/serial_test.rs new file mode 100644 index 0000000000000000000000000000000000000000..2e6d8c99788cd900d648866b880da4a7c4728c70 --- /dev/null +++ b/concurrent-btree/tests/serial_test.rs @@ -0,0 +1,47 @@ +use btree::BLinkTree; + +#[test] +fn test_serial_insert() { + let tree = BLinkTree::new(100, (i32::MIN, i32::MIN), (i32::MAX, i32::MAX)); + // insert + for i in 0..50000 { + assert_eq!(None, tree.insert(i, i)); + } + assert_eq!(50002, tree.len()); + for i in (50000..100000).rev() { + assert_eq!(None, tree.insert(i, i)); + } + assert_eq!(100002, tree.len()); + // get + for i in 0..100000 { + assert_eq!(i, *tree.get(&i).unwrap()); + } + // insert and get old value + for i in 0..100000 { + assert_eq!(i, *tree.insert(i, 100000 - i).unwrap()); + } + assert_eq!(100002, tree.len()); +} + +#[test] +fn test_serial_remove() { + let tree = BLinkTree::new(100, (i32::MIN, i32::MIN), (i32::MAX, i32::MAX)); + // insert + for i in 0..50000 { + assert_eq!(None, tree.insert(i, i)); + } + assert_eq!(50002, tree.len()); + for i in (50000..100000).rev() { + assert_eq!(None, tree.insert(i, i)); + } + assert_eq!(100002, tree.len()); + // get + for i in 0..100000 { + assert_eq!(i, *tree.get(&i).unwrap()); + } + // remove + for i in 0..50000 { + assert_eq!(i, *tree.remove(&i).unwrap()); + } + assert_eq!(50002, tree.len()); +}