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