embed_btree/lib.rs
1#![allow(rustdoc::redundant_explicit_links)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![cfg_attr(docsrs, allow(unused_attributes))]
4#![cfg_attr(not(feature = "std"), no_std)]
5
6//! # embed-btree
7//!
8//! We provide a `BTreeMap` for single-threaded in-memory storage.
9//! It's b+tree designed
10//!
11//! - cache-aware principles,
12//! - All page is aligned in 4*CACHE_LINE (256 bytes on x86_64).
13//! - Use Layout API to determine the capacity, offset and alignment for the keys and values.
14//! - Keys is a tight array to enable efficient forward search for CPU pipeline.
15//! - No parent pointer in the page, saving the space.
16//! - we fill reused PathBuffer during descending, and pop when working upward.
17//! - PathBuffer only used during ops that require mutation of the map, so it's safe for
18//! concurrent reads.
19//! - Avoid memory fragmentation for the allocator.
20//!
21//! There tree variants:
22//! - [various_map]:
23//! - Delay page allocation by inlining K, V with option.
24//! - Fallback to std BTreeMap after inserting the 2nd element
25//! - [compact]
26//! - For short-lived, small size tree.
27//! - Avoid allocation of PathBuffer on heap, until the tree height grows pass threshold.
28//! (>4 for insert/remove, >2 for entry ops)
29//! - PathBuffer allocation is reused.
30//! - [large]
31//! - For long-lived, large size tree.
32//! - after tree height grows beyond 1, allocate a small heap for PathBuffer, and page statistic (inter and
33//! leavse).
34//!
35//! ## Supported K, V types
36//!
37//! **Optimised for numeric key**
38//! - Respecting numeric space, more compact tree when doing sequential insertion
39//! - Reduce latency for sequential get and insertion.
40//!
41//! Optimised for unbalanced size K / V, and specially 0-size V, for most capacity.
42//!
43//! Bytes keys on the heap are supported, but we will not do prefix compress.
44//! (You may look for other structures with prefix compression: Art, Masstree)
45//!
46//! **Limits**:
47//! - K should have `Clone` (for propagate into the InterNode during split)
48//! - K + V should < 2 * CACHE_LINE_SIZE
49//! - compile error will be raised if size requirement not met.
50//! - It make sure InterNode can hold at least two children.
51//! - If K & V is large you should put into `Box`, for room saving, and for the speed to move value
52//! - **K should not contains and reference**.
53//! - Although our `BTreeMap<K, ()>` will act like a btree set, it should only work for integer and string
54//! (or composite) keys.
55//! - **DO NOT use `Arc<T>`** as K, it might still keep the reference after value
56//! is removed from the map (only can ganrantee release until all InterNode is destroy).
57//!
58//! ## Compared to std btree_map
59//!
60//! (Analyse based on source of rust 1.94)
61//!
62//! std:
63//! - pure btree (without horizontal links).
64//! - Each key store only once at either leaf and inter nodes, don't require key to be `Clone`.
65//! - good for point lookup (value may be at top level)
66//! - has fixed Cap=11, node size varies according to T. (For T=U64, size is 288B for InterNode and 192B for LeafNode)
67//! - the size of page varies for different type of K / V, might not perfectly aligned for the cache and allocator.
68//! - each page has keys, values, pointers. the section of pointers may be wasted for leaves.
69//! - cursor API is still unstable, need nightly.
70//!
71//! crate::bptree:
72//! - faster sequential get & insert
73//! - faster iteration
74//! - faster teardown
75//! - higher fan-out, reduction in height
76//!
77//! Struct size:
78//!
79//! ```text
80//! std: BTreeMap<u32, u32>: 24
81//! std: Option<BTreeMap<u32, u32>>: 32
82//! std: VacantEntry<u32, u32>: 40
83//! std: OccupiedEntry<u32, u32>: 32
84//!
85//! compact: BTreeMap<u32, u32>: 24
86//! compact: Option<BTreeMap<u32, u32>>: 24
87//! compact: Entry<u32,u32>: 32
88//! compact: Option<Entry<u32, u32>>: 40
89//! compact: VacantEntry<u32, u32>: 32
90//! compact: OccupiedEntry<u32, u32>: 24
91//!
92//! large: BTreeMap 24
93//! large: Option<BTreeMap> 24
94//! large: Entry size: 24
95//! large: Option<Entry>: 32
96//! large: VacantEntry<u32, u32>: 24
97//! large: OccupiedEntry<u32, u32>: 16
98//!
99//! ```
100//!
101//! ## Special API and Scenario
102//!
103//! statistic (node count and memory usage) in [large] variant
104//!
105//! Entries:
106//! - Adjacent `Entry` (for iter and modification).
107//! - [Entry::peek_forward()](bptree::Entry::peek_forward)
108//! - [Entry::peek_backward()](bptree::Entry::peek_backward)
109//! - [Entry::move_forward()](bptree::Entry::move_forward)
110//! - [Entry::move_backward()](bptree::Entry::move_backward)
111//! - [VacantEntry::peek_forward()](bptree::VacantEntry::peek_forward)
112//! - [VacantEntry::peek_backward()](bptree::VacantEntry::peek_backward)
113//! - [VacantEntry::move_forward()](bptree::VacantEntry::move_forward)
114//! - [VacantEntry::move_backward()](bptree::VacantEntry::move_backward)
115//! - [OccupiedEntry::peek_forward()](bptree::OccupiedEntry::peek_forward)
116//! - [OccupiedEntry::peek_backward()](bptree::OccupiedEntry::peek_backward)
117//! - [OccupiedEntry::move_forward()](bptree::OccupiedEntry::move_forward)
118//! - [OccupiedEntry::move_backward()](bptree::OccupiedEntry::move_backward)
119//! - Alter key of an OccupiedEntry.
120//! - [OccupiedEntry::alter_key()](bptree::OccupiedEntry::alter_key)
121//!
122//! Batch removal:
123//! - [BTree::remove_range()](bptree::BTree::remove_range)
124//! - [BTree::remove_range_with()](b[tree::BTree::remove_range_with)
125//!
126//! Readonly [Cursor](bptree::Cursor):
127//! - [BTree::cursor()](bptree::BTree::cursor)
128//! - [BTree::first_cursor()](bptree::BTree::first_cursor)
129//! - [BTree::last_cursor()](bptree::BTree::last_cursor)
130//!
131//! Use case:
132//! - [range-tree-rs](https://docs.rs/range-tree-rs)
133//!
134//! ## benchmark
135//!
136//! platform: intel i7-8550U, key: u32, value: u32, rust 1.97.
137//!
138//! Measured in million ops for different size of dataset:
139//!
140//! insert_seq |compact|large|std
141//! -|-|-|-
142//! 1k|84.777|**91.140**|20.001
143//! 10k|72.022|**79.060**|16.04
144//! 1m|44.863|**46.200**|11.207
145//!
146//! insert_rand|compact|large|std|avl(box)|avl(arc)
147//! -|-|-|-|-|-
148//! 1k|21.459|**22.284**|17.792|11.172|9.5397
149//! 10k|13.931|**14.319**|11.587|6.3669|5.651
150//! 1M|5.8124|**5.9710**|3.0691|0.78|0.732
151//!
152//! get_seq|compact|large|std
153//! -|-|-|-
154//! 1k|59.214|**62.891**|34.248
155//! 10k|40.652|**41.823**|27.571
156//! 1M|30.640|**32.597**|19.907
157//!
158//! get_rand|compact|large|std|avl(box)|avl(arc)
159//! -|-|-|-|-|-
160//! 1k|**52.213**|51.230|27.651|24.254|23.466
161//! 10k|**22.117**|20.894|16.868|11.771|10.806
162//! 1M|6.1369|**6.2403**|3.2569|1.4423|1.2712
163//!
164//! remove_rand|compact|large|std
165//! -|-|-|-
166//! 1k|18.336|**19.963**|15.968
167//! 10k|14.094|**15.462**|11.701
168//! 1M|5.2262|**5.4713**|3.0724
169//!
170//! iter|compact|large|std
171//! -|-|-|-
172//! 1k|1423,3|1443.2|346.8
173//! 10k|1226.3|1286.9|303.83
174//! 1M|**217.10**|**215.95**|51.147
175//!
176//! into_iter|compact|large|std
177//! -|-|-|-
178//! 1k|364.14|**396.07**|143.81
179//! 10k|356.85|**410.32**|81.389
180//! 1M|327.87|**360.18**|56.742
181
182extern crate alloc;
183#[cfg(any(feature = "std", test))]
184extern crate std;
185
186#[allow(private_interfaces)]
187pub mod various_map;
188pub use various_map::VariousMap;
189/// generic interface of b+tree
190pub mod bptree;
191/// BtreeMap with no stats (for many short-live dataset)
192pub mod compact;
193/// BtreeMap with stats (for large love-live dataset)
194pub mod large;
195pub use bptree::{Key, Value};
196
197pub type BTreeMap<K, V> = compact::BTreeMap<K, V>;
198
199pub use embed_collections::CACHE_LINE_SIZE;
200
201/// logging macro for development
202#[macro_export(local_inner_macros)]
203macro_rules! trace_log {
204 ($($arg:tt)+)=>{
205 #[cfg(feature="trace_log")]
206 {
207 log::debug!($($arg)+);
208 }
209 };
210}
211
212/// logging macro for development
213#[macro_export(local_inner_macros)]
214macro_rules! print_log {
215 ($($arg:tt)+)=>{
216 #[cfg(feature="trace_log")]
217 {
218 log::debug!($($arg)+);
219 }
220 #[cfg(not(feature="trace_log"))]
221 {
222 std::println!($($arg)+);
223 }
224 };
225}