From 86b40cb363125c04a52417b061df3da85a07dbf6 Mon Sep 17 00:00:00 2001 From: HeimingZ Date: Thu, 29 Sep 2022 15:41:24 +0800 Subject: [PATCH 1/3] Implement basic functions --- concurrent-btree/.gitignore | 10 + concurrent-btree/Cargo.toml | 9 + concurrent-btree/LICENSE | 21 + concurrent-btree/README.md | 2 + concurrent-btree/src/b_link_tree.rs | 659 ++++++++++++++++++++++ concurrent-btree/src/lib.rs | 112 ++++ concurrent-btree/tests/concurrent_test.rs | 110 ++++ concurrent-btree/tests/serial_test.rs | 47 ++ 8 files changed, 970 insertions(+) create mode 100644 concurrent-btree/Cargo.toml create mode 100644 concurrent-btree/LICENSE create mode 100644 concurrent-btree/README.md create mode 100644 concurrent-btree/src/b_link_tree.rs create mode 100644 concurrent-btree/src/lib.rs create mode 100644 concurrent-btree/tests/concurrent_test.rs create mode 100644 concurrent-btree/tests/serial_test.rs diff --git a/concurrent-btree/.gitignore b/concurrent-btree/.gitignore index e69de29..088ba6b 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 0000000..9d783bd --- /dev/null +++ b/concurrent-btree/Cargo.toml @@ -0,0 +1,9 @@ +[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" \ No newline at end of file diff --git a/concurrent-btree/LICENSE b/concurrent-btree/LICENSE new file mode 100644 index 0000000..ab30ee0 --- /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 0000000..6f1a10e --- /dev/null +++ b/concurrent-btree/README.md @@ -0,0 +1,2 @@ +# btree +high-performance & lock-free b-tree diff --git a/concurrent-btree/src/b_link_tree.rs b/concurrent-btree/src/b_link_tree.rs new file mode 100644 index 0000000..9b74ef2 --- /dev/null +++ b/concurrent-btree/src/b_link_tree.rs @@ -0,0 +1,659 @@ +use log::warn; +use std::{ + mem::swap, + ops::RangeBounds, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, + }, +}; + +/// 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: K, + // 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(V), +} + +/// Search status +enum SearchResult { + // target key has its corresponding value + Found(V), + // 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 leaf_nodes_tail = Arc::new(Node::new( + true, + kv_pair2.0.clone(), + None, + vec![kv_pair2.0.clone()], + vec![ValueCell::Value(kv_pair2.1)], + )); + let leaf_nodes_head = Arc::new(Node::new( + true, + kv_pair1.0.clone(), + Some(Arc::clone(&leaf_nodes_tail)), + vec![kv_pair1.0.clone()], + vec![ValueCell::Value(kv_pair1.1)], + )); + // construct tree + BLinkTree { + half_maximum_entries, + root: RwLock::new(Arc::new(Node::new( + false, + kv_pair2.0.clone(), + None, + vec![kv_pair2.0.clone()], + 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!(Some("b"), tree.insert(1, "c")); + /// assert_eq!(Some("c"), tree.get(&1)); + /// ``` + pub fn insert(&self, key: K, value: V) -> Option { + let mut path_stack = Vec::new(); + // init old node and current node + let root_rg = self.root.read().unwrap(); + 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(&key); + match search_result { + SearchResult::GoDown(next) => { + path_stack.push(Arc::clone(&old_node)); + current_node = Arc::clone(&next); + if old_node_rg.high_key < key { + drop(old_node_rg); + let mut old_node_wg = old_node.content.write().unwrap(); + old_node_wg.high_key = key.clone(); + } + } + SearchResult::GoRight(next) => current_node = Arc::clone(&next), + _ => { + warn!("Fail to find the leaf node. Skip this insert operation"); + return None; + } + } + } + + let mut k = key; + let mut v: ValueCell = ValueCell::Value(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().unwrap() }; + // 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().unwrap() }; + 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().unwrap() }; + } 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().unwrap() }; + 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().unwrap(); + 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!(Some("a"), tree.remove(&1)); + /// 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().unwrap(); + 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!(Some("a"), tree.get(&1)); + /// 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: K, + 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().unwrap(); + let key_pos = &node_rg.keys.binary_search(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(val.clone()), 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(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(val.clone()) + } 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: K, + 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(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 0000000..d451ecf --- /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 0000000..52b8070 --- /dev/null +++ b/concurrent-btree/tests/concurrent_test.rs @@ -0,0 +1,110 @@ +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!(Some(i), tree_arc.get(&i), "get {} fails.", i); + } + // insert and get old value + for i in 1..100001 { + if i % 10 != 10 - thread_i { + continue; + } + assert_eq!( + Some(i), + tree_arc.insert(i, 100001 - i), + "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!(Some(i), tree_arc.get(&i), "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!(Some(i), tree_arc.remove(&i), "remove {} fails.", i); + } + }); + handles.push(handle); + } + // wait all threads done + for handle in handles { + handle.join().unwrap(); + } + assert_eq!(2, tree.len()); +} diff --git a/concurrent-btree/tests/serial_test.rs b/concurrent-btree/tests/serial_test.rs new file mode 100644 index 0000000..143135c --- /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!(Some(i), tree.get(&i)); + } + // insert and get old value + for i in 0..100000 { + assert_eq!(Some(i), tree.insert(i, 100000 - i)); + } + 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!(Some(i), tree.get(&i)); + } + // remove + for i in 0..50000 { + assert_eq!(Some(i), tree.remove(&i)); + } + assert_eq!(50002, tree.len()); +} -- Gitee From c643002cba151d5dc97fa5d317ef02aade458f5f Mon Sep 17 00:00:00 2001 From: HeimingZ Date: Tue, 25 Oct 2022 22:15:07 +0800 Subject: [PATCH 2/3] use Arc to store keys and values --- concurrent-btree/Cargo.toml | 8 +- concurrent-btree/src/b_link_tree.rs | 107 +++++++++++----------- concurrent-btree/tests/concurrent_test.rs | 26 ++++-- concurrent-btree/tests/serial_test.rs | 8 +- 4 files changed, 86 insertions(+), 63 deletions(-) diff --git a/concurrent-btree/Cargo.toml b/concurrent-btree/Cargo.toml index 9d783bd..02f4e0c 100644 --- a/concurrent-btree/Cargo.toml +++ b/concurrent-btree/Cargo.toml @@ -6,4 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4" \ No newline at end of file +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/src/b_link_tree.rs b/concurrent-btree/src/b_link_tree.rs index 9b74ef2..e94d39b 100644 --- a/concurrent-btree/src/b_link_tree.rs +++ b/concurrent-btree/src/b_link_tree.rs @@ -1,16 +1,17 @@ use log::warn; +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{ mem::swap, ops::RangeBounds, sync::{ atomic::{AtomicUsize, Ordering}, - Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, + Arc, }, }; /// An ordered map based on a [B-Link-Tree]. #[derive(Debug)] -pub struct BLinkTree { +pub struct BLinkTree { // each node contains at most 2 * half_maximum_entries values half_maximum_entries: usize, // root node of this b-tree @@ -23,7 +24,7 @@ pub struct BLinkTree { /// Tree node #[derive(Debug)] -struct Node { +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 @@ -32,13 +33,13 @@ struct Node { /// Tree node contents, should be protected by read write lock #[derive(Debug)] -struct NodeContent { +struct NodeContent { // high key, an upper bound on keys stored in the subtree with this node as the root - high_key: K, + 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, + 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>, @@ -46,17 +47,17 @@ struct NodeContent { /// Value cell, stores value or node pointer #[derive(Debug)] -enum ValueCell { +enum ValueCell { // non-leaf node stores pointers to the son nodes Node(Arc>), // leaf node stores values - Value(V), + Value(Arc), } /// Search status -enum SearchResult { +enum SearchResult { // target key has its corresponding value - Found(V), + Found(Arc), // target key doesn't have its corresponding value NotFound(), // traverse to child node @@ -67,7 +68,7 @@ enum SearchResult { Error(), } -impl BLinkTree { +impl BLinkTree { /// Makes a new `BLinkTree`, with two initial entries in it. /// Each node contains at most 2 * half_maximum_entries entries. /// @@ -95,28 +96,30 @@ impl BLinkTree { 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, - kv_pair2.0.clone(), + Arc::clone(&tail_high_key_arc), None, - vec![kv_pair2.0.clone()], - vec![ValueCell::Value(kv_pair2.1)], + 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, - kv_pair1.0.clone(), + Arc::clone(&head_high_key_arc), Some(Arc::clone(&leaf_nodes_tail)), - vec![kv_pair1.0.clone()], - vec![ValueCell::Value(kv_pair1.1)], + 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, - kv_pair2.0.clone(), + Arc::clone(&tail_high_key_arc), None, - vec![kv_pair2.0.clone()], + vec![Arc::clone(&tail_high_key_arc)], vec![ ValueCell::Node(Arc::clone(&leaf_nodes_head)), ValueCell::Node(Arc::clone(&leaf_nodes_tail)), @@ -141,28 +144,29 @@ impl BLinkTree { /// assert_eq!(false, tree.is_empty()); /// /// tree.insert(1, "b"); - /// assert_eq!(Some("b"), tree.insert(1, "c")); - /// assert_eq!(Some("c"), tree.get(&1)); + /// assert_eq!("b", *tree.insert(1, "c").unwrap()); + /// assert_eq!("c", *tree.get(&1).unwrap()); /// ``` - pub fn insert(&self, key: K, value: V) -> Option { + 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().unwrap(); + 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(&key); + 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 < key { + if *old_node_rg.high_key < *k { drop(old_node_rg); - let mut old_node_wg = old_node.content.write().unwrap(); - old_node_wg.high_key = key.clone(); + 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), @@ -173,11 +177,10 @@ impl BLinkTree { } } - let mut k = key; - let mut v: ValueCell = ValueCell::Value(value); + 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().unwrap() }; + 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); @@ -188,7 +191,7 @@ impl BLinkTree { 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().unwrap() }; + current_node_wg = unsafe { (*current_node_content_ptr).write() }; drop(old_node_wg); } else { break; @@ -259,7 +262,7 @@ impl BLinkTree { 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().unwrap() }; + current_node_wg = unsafe { (*current_node_content_ptr).write() }; } else { break; } @@ -273,7 +276,7 @@ impl BLinkTree { 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().unwrap() }; + current_node_wg = unsafe { (*current_node_content_ptr).write() }; drop(old_node_wg); } else { break; @@ -289,7 +292,7 @@ impl BLinkTree { fn find_leaf_node(&self, key: &K) -> Option>> { // init old node and current node - let root_rg = self.root.read().unwrap(); + let root_rg = self.root.read(); let mut current_node = Arc::clone(&root_rg); drop(root_rg); let mut old_node; @@ -320,10 +323,10 @@ impl BLinkTree { /// /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); /// tree.insert(1, "a"); - /// assert_eq!(Some("a"), tree.remove(&1)); + /// assert_eq!("a", *tree.remove(&1).unwrap()); /// assert_eq!(None, tree.remove(&1)); /// ``` - pub fn remove(&self, key: &K) -> Option { + pub fn remove(&self, key: &K) -> Option> { let mut current_node; if let Some(node) = self.find_leaf_node(key) { current_node = node; @@ -333,7 +336,7 @@ impl BLinkTree { // 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().unwrap(); + 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(_) => { @@ -373,10 +376,10 @@ impl BLinkTree { /// /// let tree = BLinkTree::new(5, (i32::MIN, "dummy_value"), (i32::MAX, "dummy_value")); /// tree.insert(1, "a"); - /// assert_eq!(Some("a"), tree.get(&1)); + /// assert_eq!("a", *tree.get(&1).unwrap()); /// assert_eq!(None, tree.get(&2)); /// ``` - pub fn get(&self, key: &K) -> Option { + pub fn get(&self, key: &K) -> Option> { let mut current_node; if let Some(node) = self.find_leaf_node(key) { current_node = node; @@ -467,7 +470,7 @@ impl BLinkTree { } } -impl Node { +impl Node { /// Makes a new tree node. /// Each node contains at most 2 * half_maximum_entries entries. /// @@ -478,9 +481,9 @@ impl Node { /// leaf nodes: values.len() == keys.len() pub(crate) fn new( is_leaf: bool, - high_key: K, + high_key: Arc, link_pointer: Option>>, - keys: Vec, + keys: Vec>, values: Vec>, ) -> Node { if is_leaf && values.len() != keys.len() { @@ -504,15 +507,15 @@ impl Node { &self, key: &K, ) -> (SearchResult, RwLockReadGuard>) { - let node_rg = self.content.read().unwrap(); - let key_pos = &node_rg.keys.binary_search(key); + 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(val.clone()), node_rg) + (SearchResult::Found(Arc::clone(val)), node_rg) } else { (SearchResult::Error(), node_rg) } @@ -536,7 +539,7 @@ impl Node { 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) + (SearchResult::GoDown(Arc::clone(&next)), node_rg) } else { (SearchResult::Error(), node_rg) } @@ -571,14 +574,14 @@ impl Node { key: &K, node_wg: &RwLockWriteGuard>, ) -> SearchResult { - let key_pos = node_wg.keys.binary_search(key); + 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(val.clone()) + SearchResult::Found(Arc::clone(val)) } else { SearchResult::Error() } @@ -631,11 +634,11 @@ impl Node { } } -impl NodeContent { +impl NodeContent { pub(crate) fn new( - high_key: K, + high_key: Arc, link_pointer: Option>>, - keys: Vec, + keys: Vec>, values: Vec>, ) -> NodeContent { return NodeContent { @@ -647,7 +650,7 @@ impl NodeContent { } pub(crate) fn remove(&mut self, key: &K) -> Option> { - let key_pos = self.keys.binary_search(key); + let key_pos = self.keys.binary_search_by(|probe| (**probe).cmp(key)); return match key_pos { Ok(pos) => { self.keys.remove(pos); diff --git a/concurrent-btree/tests/concurrent_test.rs b/concurrent-btree/tests/concurrent_test.rs index 52b8070..64acbda 100644 --- a/concurrent-btree/tests/concurrent_test.rs +++ b/concurrent-btree/tests/concurrent_test.rs @@ -27,7 +27,7 @@ fn test_concurrent_insert() { if i % 10 != 10 - thread_i { continue; } - assert_eq!(Some(i), tree_arc.get(&i), "get {} fails.", i); + assert_eq!(i, *tree_arc.get(&i).unwrap(), "get {} fails.", i); } // insert and get old value for i in 1..100001 { @@ -35,8 +35,8 @@ fn test_concurrent_insert() { continue; } assert_eq!( - Some(i), - tree_arc.insert(i, 100001 - i), + i, + *tree_arc.insert(i, 100001 - i).unwrap(), "insert {} fails.", i ); @@ -76,7 +76,7 @@ fn test_concurrent_remove() { if i % 10 != 10 - thread_i { continue; } - assert_eq!(Some(i), tree_arc.get(&i), "get {} fails.", i); + assert_eq!(i, *tree_arc.get(&i).unwrap(), "get {} fails.", i); } }); handles.push(handle); @@ -97,7 +97,21 @@ fn test_concurrent_remove() { if i % 10 != 10 - thread_i { continue; } - assert_eq!(Some(i), tree_arc.remove(&i), "remove {} fails.", i); + 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); @@ -106,5 +120,5 @@ fn test_concurrent_remove() { for handle in handles { handle.join().unwrap(); } - assert_eq!(2, tree.len()); + assert_eq!(100002, tree.len()); } diff --git a/concurrent-btree/tests/serial_test.rs b/concurrent-btree/tests/serial_test.rs index 143135c..2e6d8c9 100644 --- a/concurrent-btree/tests/serial_test.rs +++ b/concurrent-btree/tests/serial_test.rs @@ -14,11 +14,11 @@ fn test_serial_insert() { assert_eq!(100002, tree.len()); // get for i in 0..100000 { - assert_eq!(Some(i), tree.get(&i)); + assert_eq!(i, *tree.get(&i).unwrap()); } // insert and get old value for i in 0..100000 { - assert_eq!(Some(i), tree.insert(i, 100000 - i)); + assert_eq!(i, *tree.insert(i, 100000 - i).unwrap()); } assert_eq!(100002, tree.len()); } @@ -37,11 +37,11 @@ fn test_serial_remove() { assert_eq!(100002, tree.len()); // get for i in 0..100000 { - assert_eq!(Some(i), tree.get(&i)); + assert_eq!(i, *tree.get(&i).unwrap()); } // remove for i in 0..50000 { - assert_eq!(Some(i), tree.remove(&i)); + assert_eq!(i, *tree.remove(&i).unwrap()); } assert_eq!(50002, tree.len()); } -- Gitee From e2825f90f91a8dcf9e8cf340d087946a10d21966 Mon Sep 17 00:00:00 2001 From: HeimingZ Date: Tue, 25 Oct 2022 22:15:16 +0800 Subject: [PATCH 3/3] add benchmark --- concurrent-btree/benches/btree_benchmark.rs | 221 ++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 concurrent-btree/benches/btree_benchmark.rs diff --git a/concurrent-btree/benches/btree_benchmark.rs b/concurrent-btree/benches/btree_benchmark.rs new file mode 100644 index 0000000..c2fac79 --- /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); -- Gitee