embed_collections/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-collections
7//!
8//! A collection of memory efficient data structures, for embedding environment and server applications that need tight memory management.
9//!
10//! This crate provides two categories of modules:
11//!
12//! - Cache efficient collections:
13//! - [const_vec](crate::const_vec): Fixed capacity inline vec
14//! - [seg_list](#seglist--various): A cache aware (short-live) list to store elements with adaptive size segments
15//! - [various](#seglist--various): A short-live list wrapping `SegList` with `Option<T>` to delay allocation.
16//! - [btree](#btree): A cache aware B+tree for lone-live and large dataset, optimized for numeric types, with special entry API allows peeking adjacent values.
17//! - [various_map](crate::various_map): A short-live map wrapping BTreeMap (std) with `Option<(K, V)>` to delay allocation.
18//!
19//! - [Intrusive collections](#intrusive-collections):
20//! - Supports various smart pointer types: owned (Box), multiple ownership (Arc, Rc), raw pointers (`NonNull<T>`, `*const T`, `*mut T`)
21//! - [dlist](crate::dlist): Intrusive Doubly Linked List (Queue / Stack).
22//! - [slist](crate::slist): Intrusive Singly Linked List ( Queue / stack).
23//! - [slist_owned](crate::slist_owned): An intrusive slist but with safe and more compact interface
24//! - [avl](crate::avl): Intrusive AVL Tree (Balanced Binary Search Tree), port to rust from ZFS
25//!
26//! ## SegList & Various
27//!
28//! [SegList](crate::seg_list) and [Various](crate::various) is designed for parameter passing.
29//!
30//! More CPU-cache friendly compared to `LinkedList`. And because it does not re-allocate, it's faster than `Vec::push()` when the number of elements is small.
31//! It's nice to the memory allocator (always allocate with fixed size segment).
32//!
33//! Benchmark: append + drain (x86_64, cache line 128 bytes):
34//!
35//! (platform: intel i7-8550U)
36//!
37//! | Elements | SegList | Vec | SegList vs Vec |
38//! |----------|---------|-----|----------------|
39//! | 10 | 40.5 ns | 147.0 ns | **3.6x faster** |
40//! | 32 | 99.1 ns | 237.8 ns | **2.4x faster** |
41//! | 100 | 471.1 ns | 464.0 ns | ~1.0x |
42//! | 500 | 2.77 µs | 895.5 ns | 3.1x slower |
43//!
44//! ## B+tree
45//!
46//! We provide a [BTreeMap](crate::btree) for single-threaded long-term in-memory storage.
47//! It's a cache aware b+tree:
48//!
49//! - Nodes are filled up in 4 cache lines (256 bytes on x86_64).
50//! - Capacity in compile-time determined according to the size of Key, Value.
51//! - Reduce memory fragmentation by alignment.
52//! - **Optimised for numeric key**
53//! - Respecting numeric space for sequential insertion.
54//! - Reduce latency for sequential insertion.
55//! - Faster iteration and teardown
56//! - **Limitation**:
57//! - K should have clone (for propagate into the InterNode during split)
58//! - K & V should <= CACHE_LINE_SIZE - 16
59//! - It make sure InterNode can hold at least two children.
60//! - If K & V is large you should put into `Box`, for room saving, and for the speed to move value
61//! - **Special API**:
62//! - Peak and move to previous/next `Entry` (for modification).
63//! - Alter key of an OccupiedEntry.
64//! - Batch remove with range.
65//! - Movable `Cursor` (for readonly)
66//!
67//! Compared to std::collections::btree (as of rust 1.94):
68//! - The std impl is pure btree (not b+tree) without horizontal links. Each key store only once at either leaf and inter nodes.
69//! - The std impl is optimised for point lookup.
70//! - The std impl has fixed Cap=11, node size varies according to T. (For T=U64, size is 288B for InterNode and 192B for LeafNode)
71//! - The std cursor API is still unstable (as of 1.94) and relatively complex to use.
72//!
73//! **benchmark**
74//!
75//! platform: intel i7-8550U, key: u32, value: u32, rust 1.92.
76//!
77//! Measured in million ops for different size of dataset:
78//!
79//! insert_seq |btree|std
80//! -|-|-
81//! 1k|**88.956**|20.001
82//! 10k|**75.291**|16.04
83//! 100k|**45.959**|11.207
84//!
85//! insert_rand|btree|std|avl(box)|avl(arc)
86//! -|-|-|-|-
87//! 1k|**21.311**|17.792|11.172|9.5397
88//! 10k|**14.268**|11.587|6.3669|5.651
89//! 100k|**5.4814**|3.0691|0.78|0.732
90//!
91//! get_seq|btree|std
92//! -|-|-
93//! 1k|**59.448**|34.248
94//! 10k|**37.225**|27.571
95//! 100k|**30.77**|19.907
96//!
97//! get_rand|btree|std|avl(box)|avl(arc)
98//! -|-|-|-|-
99//! 1k|**47.33**|27.651|24.254|23.466
100//! 10k|**19.358**|16.868|11.771|10.806
101//! 100k|**5.2584**|3.2569|1.4423|1.2712
102//!
103//! remove_rand |btree|std
104//! -|-|-
105//! 1k|**20.965**|15.968
106//! 10k|**16.073**|11.701
107//! 100k|**5.0214**|3.0724
108//!
109//! iter|btree|std
110//! -|-|-
111//! 1k|1342.8|346.8
112//! 10k|1209.4|303.83
113//! 100k|**152.57**|51.147
114//!
115//! into_iter|btree|std
116//! -|-|-
117//! 1k|396.07|143.81
118//! 10k|397.05|81.389
119//! 100k|**360.18**|56.742
120//!
121//!
122//! ## Intrusive Collections
123//!
124//! intrusive collection is often used in c/c++ code, they does not need extra allocation.
125//! But the disadvantages includes: complexity to write, bad for cache hit when the node is too small
126//!
127//! There're three usage scenarios:
128//!
129//! 1. Push smart pointer to the list, so that the list hold 1 ref count when the type is `Arc` /
130//! `Rc`, but you have to use UnsafeCell for internal mutation.
131//!
132//! 2. Push `Box` to the list, the list own the items until they are popped, it's better than std
133//! LinkedList because no additional allocation is needed.
134//!
135//! 3. Push raw pointer (better use NonNull instead of *const T for smaller footprint) to the list,
136//! for temporary usage. You must ensure the list item not dropped be other refcount
137//! (for example, the item is holding by Arc in other structure).
138//!
139//!
140//! ### Difference to `intrusive-collections` crate
141//!
142//! This crate choose to use trait instead of c like `offset_of!`, mainly because:
143//!
144//! - Mangling with offset conversion makes the code hard to read (for people not used to c style coding).
145//!
146//! - You don't have to understand some complex macro style.
147//!
148//! - It's dangerous to use pointer offset conversion when the embedded Node not perfectly aligned,
149//! and using memtion to return the node ref is more safer approach.
150//! For example, the default `repr(Rust)` might reorder the field, or you mistakenly use `repr(packed)`.
151//!
152//! ### instrusive link list example
153//!
154//! ```rust
155//! use embed_collections::{dlist::{DLinkedList, DListItem, DListNode}, Pointer};
156//! use std::cell::UnsafeCell;
157//! use std::sync::Arc;
158//!
159//! // The tag structure only for labeling, distinguish two lists
160//! struct CacheTag;
161//! struct IOTag;
162//!
163//! struct MyItem {
164//! val: i32,
165//! cache_link: UnsafeCell<DListNode<MyItem, CacheTag>>,
166//! io_link: UnsafeCell<DListNode<MyItem, IOTag>>,
167//! }
168//!
169//! impl MyItem {
170//! fn new(val: i32) -> Self {
171//! Self {
172//! val,
173//! cache_link: UnsafeCell::new(DListNode::default()),
174//! io_link: UnsafeCell::new(DListNode::default()),
175//! }
176//! }
177//! }
178//!
179//! unsafe impl DListItem<CacheTag> for MyItem {
180//! fn get_node(&self) -> &mut DListNode<Self, CacheTag> {
181//! unsafe { &mut *self.cache_link.get() }
182//! }
183//! }
184//!
185//! unsafe impl DListItem<IOTag> for MyItem {
186//! fn get_node(&self) -> &mut DListNode<Self, IOTag> {
187//! unsafe { &mut *self.io_link.get() }
188//! }
189//! }
190//!
191//! let mut cache_list = DLinkedList::<Arc<MyItem>, CacheTag>::new();
192//! let mut io_list = DLinkedList::<Arc<MyItem>, IOTag>::new();
193//!
194//! let item1 = Arc::new(MyItem::new(10));
195//! let item2 = Arc::new(MyItem::new(20));
196//!
197//! // Push the same item to both lists
198//! cache_list.push_back(item1.clone());
199//! io_list.push_back(item1.clone());
200//!
201//! cache_list.push_back(item2.clone());
202//! io_list.push_back(item2.clone());
203//!
204//! assert_eq!(cache_list.pop_front().unwrap().val, 10);
205//! assert_eq!(io_list.pop_front().unwrap().val, 10);
206//! assert_eq!(cache_list.pop_front().unwrap().val, 20);
207//! assert_eq!(io_list.pop_front().unwrap().val, 20);
208//! ```
209//!
210//! ## Feature Flags
211//!
212//! * **`default`**: Enabled by default. Includes the `std`, `various`, `seglist` features.
213//! * **`std`**: Enables integration with the Rust standard library, including the `println!` macro for debugging. Disabling this feature enables `no_std` compilation.
214//! * **`seglist`**: Enable seg_list module
215//! * **`various`**: Enable various_map module
216//! * **`various` + `seglist`**: Enable various module
217//! * **`slist`**: Enables the singly linked list (`slist`) and owned singly linked list (`slist_owned`) modules.
218//! * **`dlist`**: Enables the doubly linked list (`dlist`) module.
219//! * **`avl`**: Enables the `avl` module.
220//! * **`btree`**: Enable the btree module.
221//! * **`full`**: Enabled by default. Includes `slist`, `dlist`, and `avl`.
222//!
223//! To compile with `no_std` and only the `slist` module, you would use:
224//! `cargo build --no-default-features --features slist`
225
226extern crate alloc;
227#[cfg(any(feature = "std", test))]
228extern crate std;
229
230use alloc::boxed::Box;
231use alloc::rc::Rc;
232use alloc::sync::Arc;
233use core::ptr::NonNull;
234
235/// Abstract pointer trait to support various pointer types in collections.
236///
237/// This trait allows the collections to work with:
238/// - `Box<T>`: Owned, automatically dropped.
239/// - `Arc<T>`: Shared ownership.
240/// - `Rc<T>`: Single thread ownership.
241/// - `NonNull<T>`: Raw non-null pointers (manual memory management).
242/// - `*const T`: Raw pointers (recommend to use `NonNull<T>` instead)
243pub trait Pointer: Sized {
244 type Target;
245
246 fn as_ref(&self) -> &Self::Target;
247
248 #[inline(always)]
249 fn as_ptr(&self) -> *const Self::Target {
250 self.as_ref() as *const Self::Target
251 }
252
253 #[allow(clippy::missing_safety_doc)]
254 unsafe fn from_raw(p: *const Self::Target) -> Self;
255
256 fn into_raw(self) -> *const Self::Target;
257}
258
259pub trait SmartPointer: Pointer {
260 fn new(t: Self::Target) -> Self;
261}
262
263#[allow(clippy::unnecessary_cast)]
264impl<T> Pointer for *const T {
265 type Target = T;
266
267 #[inline]
268 fn as_ref(&self) -> &Self::Target {
269 unsafe { &**self }
270 }
271
272 #[inline]
273 unsafe fn from_raw(p: *const Self::Target) -> Self {
274 p as *const T
275 }
276
277 #[inline]
278 fn into_raw(self) -> *const Self::Target {
279 self as *const T
280 }
281}
282
283#[allow(clippy::unnecessary_cast)]
284impl<T> Pointer for *mut T {
285 type Target = T;
286
287 #[inline]
288 fn as_ref(&self) -> &Self::Target {
289 unsafe { &**self }
290 }
291
292 #[inline]
293 unsafe fn from_raw(p: *const Self::Target) -> Self {
294 p as *mut T
295 }
296
297 #[inline]
298 fn into_raw(self) -> *const Self::Target {
299 self as *mut T
300 }
301}
302
303impl<T> Pointer for NonNull<T> {
304 type Target = T;
305
306 #[inline]
307 fn as_ref(&self) -> &Self::Target {
308 unsafe { self.as_ref() }
309 }
310
311 #[inline]
312 unsafe fn from_raw(p: *const Self::Target) -> Self {
313 unsafe { NonNull::new_unchecked(p as *mut T) }
314 }
315
316 #[inline]
317 fn into_raw(self) -> *const Self::Target {
318 self.as_ptr()
319 }
320}
321
322impl<T> Pointer for Box<T> {
323 type Target = T;
324
325 #[inline]
326 fn as_ref(&self) -> &Self::Target {
327 self
328 }
329
330 #[inline]
331 unsafe fn from_raw(p: *const Self::Target) -> Self {
332 unsafe { Box::from_raw(p as *mut T) }
333 }
334
335 #[inline]
336 fn into_raw(self) -> *const Self::Target {
337 Box::into_raw(self)
338 }
339}
340
341impl<T> SmartPointer for Box<T> {
342 #[inline]
343 fn new(inner: T) -> Self {
344 Box::new(inner)
345 }
346}
347
348impl<T> Pointer for Rc<T> {
349 type Target = T;
350
351 #[inline]
352 fn as_ref(&self) -> &Self::Target {
353 self
354 }
355
356 #[inline]
357 unsafe fn from_raw(p: *const Self::Target) -> Self {
358 unsafe { Rc::from_raw(p) }
359 }
360
361 #[inline]
362 fn into_raw(self) -> *const Self::Target {
363 Rc::into_raw(self)
364 }
365}
366
367impl<T> SmartPointer for Rc<T> {
368 #[inline]
369 fn new(inner: T) -> Self {
370 Rc::new(inner)
371 }
372}
373
374impl<T> Pointer for Arc<T> {
375 type Target = T;
376
377 #[inline]
378 fn as_ref(&self) -> &Self::Target {
379 self
380 }
381
382 #[inline]
383 unsafe fn from_raw(p: *const Self::Target) -> Self {
384 unsafe { Arc::from_raw(p) }
385 }
386
387 #[inline]
388 fn into_raw(self) -> *const Self::Target {
389 Arc::into_raw(self)
390 }
391}
392
393impl<T> SmartPointer for Arc<T> {
394 #[inline]
395 fn new(inner: T) -> Self {
396 Arc::new(inner)
397 }
398}
399
400impl<T> Pointer for &T {
401 type Target = T;
402
403 #[inline]
404 fn as_ref(&self) -> &Self::Target {
405 self
406 }
407
408 #[inline]
409 unsafe fn from_raw(p: *const Self::Target) -> Self {
410 unsafe { &*p }
411 }
412
413 #[inline]
414 fn into_raw(self) -> *const Self::Target {
415 self as *const T
416 }
417}
418
419#[cfg(feature = "avl")]
420pub mod avl;
421pub mod const_vec;
422pub use const_vec::ConstVec;
423#[cfg(feature = "dlist")]
424pub mod dlist;
425#[cfg(feature = "seglist")]
426pub mod seg_list;
427#[cfg(feature = "seglist")]
428pub use seg_list::SegList;
429#[cfg(feature = "slist")]
430pub mod slist;
431#[cfg(feature = "slist")]
432pub mod slist_owned;
433#[cfg(all(feature = "various", feature = "seglist"))]
434pub mod various;
435#[cfg(all(feature = "various", feature = "seglist"))]
436pub use various::Various;
437#[cfg(feature = "various")]
438#[allow(private_interfaces)]
439pub mod various_map;
440#[cfg(feature = "various")]
441pub use various_map::VariousMap;
442#[cfg(feature = "btree")]
443pub mod btree;
444#[cfg(feature = "btree")]
445pub use btree::BTreeMap;
446
447#[cfg(test)]
448pub mod test;
449
450/// Cache line size in bytes
451#[cfg(any(
452 target_arch = "x86_64",
453 target_arch = "aarch64",
454 target_arch = "arm64ec",
455 target_arch = "powerpc64",
456))]
457pub const CACHE_LINE_SIZE: usize = 64;
458#[cfg(not(any(
459 target_arch = "x86_64",
460 target_arch = "aarch64",
461 target_arch = "arm64ec",
462 target_arch = "powerpc64",
463)))]
464pub const CACHE_LINE_SIZE: usize = 32;
465
466/// logging macro for development
467#[macro_export(local_inner_macros)]
468macro_rules! trace_log {
469 ($($arg:tt)+)=>{
470 #[cfg(feature="trace_log")]
471 {
472 log::debug!($($arg)+);
473 }
474 };
475}
476
477/// logging macro for development
478#[macro_export(local_inner_macros)]
479macro_rules! print_log {
480 ($($arg:tt)+)=>{
481 #[cfg(feature="trace_log")]
482 {
483 log::debug!($($arg)+);
484 }
485 #[cfg(not(feature="trace_log"))]
486 {
487 std::println!($($arg)+);
488 }
489 };
490}