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