Skip to main content

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