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//! Struct size:
36//!
37//! ```text
38//! std: BTreeMap<u32, u32>: 24
39//! std: Option<BTreeMap<u32, u32>>: 32
40//! std: VacantEntry<u32, u32>: 40
41//! std: OccupiedEntry<u32, u32>: 32
42//!
43//! compact: BTreeMap<u32, u32>: 24
44//! compact: Option<BTreeMap<u32, u32>>: 24
45//! compact: Entry<u32,u32>: 32
46//! compact: Option<Entry<u32, u32>>: 40
47//! compact: VacantEntry<u32, u32>: 32
48//! compact: OccupiedEntry<u32, u32>: 24
49//!
50//! large: BTreeMap 24
51//! large: Option<BTreeMap> 24
52//! large: Entry size: 24
53//! large: Option<Entry>: 32
54//! large: VacantEntry<u32, u32>: 24
55//! large: OccupiedEntry<u32, u32>: 16
56//!
57//! ```
58//!
59//! ## Supported K, V types
60//!
61//! **Optimised for numeric key**
62//! - Respecting numeric space, more compact tree when doing sequential insertion
63//! - Reduce latency for sequential get and insertion.
64//!
65//! Optimised for unbalanced size K / V, and specially 0-size V, for most capacity.
66//!
67//! Bytes keys on the heap are supported, but we will not do prefix compress.
68//! (You may look for other structures with prefix compression: Art, Masstree)
69//!
70//! Limits:
71//! - K should have `Clone` (for propagate into the InterNode during split)
72//! - K + V should < 2 * CACHE_LINE_SIZE
73//! - It make sure InterNode can hold at least two children.
74//! - If K & V is large you should put into `Box`, for room saving, and for the speed to move value
75//!
76//! ## Compared to std btree_map
77//!
78//! (Analyse based on source of rust 1.94)
79//!
80//! std:
81//! - pure btree (without horizontal links).
82//! - Each key store only once at either leaf and inter nodes, don't require key to be `Clone`.
83//! - good for point lookup (value may be at top level)
84//! - has fixed Cap=11, node size varies according to T. (For T=U64, size is 288B for InterNode and 192B for LeafNode)
85//! - the size of page varies for different type of K / V, might not perfectly aligned for the cache and allocator.
86//! - each page has keys, values, pointers. the section of pointers may be wasted for leaves.
87//! - cursor API is still unstable, need nightly.
88//!
89//! crate::bptree:
90//! - faster sequential get & insert
91//! - faster iteration
92//! - faster teardown
93//! - higher fan-out, reduction in height
94//!
95//! ## Special API and Scenario
96//!
97//! statistic (node count and memory usage) in [large] variant
98//!
99//! Entries:
100//! - Adjacent `Entry` (for iter and modification).
101//! - [bptree::Entry::peek_forward()]
102//! - [bptree::Entry::peek_backward()]
103//! - [bptree::Entry::move_forward()]
104//! - [bptree::Entry::move_backward()]
105//! - [bptree::VacantEntry::peek_forward()]
106//! - [bptree::VacantEntry::peek_backward()]
107//! - [bptree::VacantEntry::move_forward()]
108//! - [bptree::VacantEntry::move_backward()]
109//! - [bptree::OccupiedEntry::peek_forward()]
110//! - [bptree::OccupiedEntry::peek_backward()]
111//! - [bptree::OccupiedEntry::move_forward()]
112//! - [bptree::OccupiedEntry::move_backward()]
113//! - Alter key of an OccupiedEntry.
114//! - [bptree::OccupiedEntry::alter_key()]
115//!
116//! Batch removal:
117//! - [bptree::BTree::remove_range()](btree::BTree::remove_range)
118//! - [bptree::BTree::remove_range_with()](btree::BTree::remove_range_with)
119//!
120//! Readonly [Cursor](btree::Cursor):
121//! - [bptree::BTree::cursor()](btree::BTree::cursor)
122//! - [bptree::BTree::first_cursor()](btree::BTree::first_cursor)
123//! - [bptree::BTree::last_cursor()](btree::BTree::last_cursor)
124//!
125//! Use case:
126//! - [range-tree-rs](https://docs.rs/range-tree-rs)
127//!
128//! ## benchmark
129//!
130//! platform: intel i7-8550U, key: u32, value: u32, rust 1.97.
131//!
132//! Measured in million ops for different size of dataset:
133//!
134//! insert_seq |compact|large|std
135//! -|-|-|-
136//! 1k|84.777|**91.140**|20.001
137//! 10k|72.022|**79.060**|16.04
138//! 1m|44.863|**46.200**|11.207
139//!
140//! insert_rand|compact|large|std|avl(box)|avl(arc)
141//! -|-|-|-|-|-
142//! 1k|21.459|**22.284**|17.792|11.172|9.5397
143//! 10k|13.931|**14.319**|11.587|6.3669|5.651
144//! 1M|5.8124|**5.9710**|3.0691|0.78|0.732
145//!
146//! get_seq|compact|large|std
147//! -|-|-|-
148//! 1k|59.214|**62.891**|34.248
149//! 10k|40.652|**41.823**|27.571
150//! 1M|30.640|**32.597**|19.907
151//!
152//! get_rand|compact|large|std|avl(box)|avl(arc)
153//! -|-|-|-|-|-
154//! 1k|**52.213**|51.230|27.651|24.254|23.466
155//! 10k|**22.117**|20.894|16.868|11.771|10.806
156//! 1M|6.1369|**6.2403**|3.2569|1.4423|1.2712
157//!
158//! remove_rand|compact|large|std
159//! -|-|-|-
160//! 1k|18.336|**19.963**|15.968
161//! 10k|14.094|**15.462**|11.701
162//! 1M|5.2262|**5.4713**|3.0724
163//!
164//! iter|compact|large|std
165//! -|-|-|-
166//! 1k|1423,3|1443.2|346.8
167//! 10k|1226.3|1286.9|303.83
168//! 1M|**217.10**|**215.95**|51.147
169//!
170//! into_iter|compact|large|std
171//! -|-|-|-
172//! 1k|364.14|**396.07**|143.81
173//! 10k|356.85|**410.32**|81.389
174//! 1M|327.87|**360.18**|56.742
175
176extern crate alloc;
177#[cfg(any(feature = "std", test))]
178extern crate std;
179
180#[allow(private_interfaces)]
181pub mod various_map;
182pub use various_map::VariousMap;
183/// generic interface of b+tree
184pub mod bptree;
185/// BtreeMap with no stats (for many short-live dataset)
186pub mod compact;
187/// BtreeMap with stats (for large love-live dataset)
188pub mod large;
189pub use bptree::{Key, Value};
190
191pub type BTreeMap<K, V> = compact::BTreeMap<K, V>;
192
193pub use embed_collections::CACHE_LINE_SIZE;
194
195/// logging macro for development
196#[macro_export(local_inner_macros)]
197macro_rules! trace_log {
198 ($($arg:tt)+)=>{
199 #[cfg(feature="trace_log")]
200 {
201 log::debug!($($arg)+);
202 }
203 };
204}
205
206/// logging macro for development
207#[macro_export(local_inner_macros)]
208macro_rules! print_log {
209 ($($arg:tt)+)=>{
210 #[cfg(feature="trace_log")]
211 {
212 log::debug!($($arg)+);
213 }
214 #[cfg(not(feature="trace_log"))]
215 {
216 std::println!($($arg)+);
217 }
218 };
219}