mule_map/lib.rs
1//! <p align="center">
2//! <img src="https://raw.githubusercontent.com/gringasalpastor/mule-map/refs/heads/master/assets/mule-with-map.png" width="200" height="200"
3//! style="border-radius:50%" />
4//! </p>
5//!
6//! # [`MuleMap`]<🫏,🗺>
7//! [](https://github.com/gringasalpastor/mule-map/actions/workflows/ci.yml)
8//! [](https://docs.rs/mule-map)
9//! [](https://crates.io/crates/mule-map)
10//!
11//! `MuleMap` is a hybrid between a `HashMap` and a lookup table. It improves performance for frequently accessed keys in a known range. If a key (integer) is in the user specified range, then its value will be stored directly in the lookup table. Benchmarks (using random selection) start to show speed improvements when about 50% of the key accesses are in the lookup table. Performance is almost identical to `HashMap` with less than 50%. `MuleMap` tries to match the API of the standard library `HashMap` - making it a drop-in replacement for `HashMap`.
12//!
13//! ## Example
14//!
15//!
16//! ```rust
17//! use mule_map::MuleMap;
18//! use std::num::NonZero;
19//! type Hash = fnv_rs::FnvBuildHasher;  // Use whatever hash function you prefer
20//!
21//! // Using Entry API
22//! let mut mule_map = MuleMap::<u32, usize, Hash>::new();
23//! assert_eq!(mule_map.get(5), None);
24//! let entry = mule_map.entry(5);
25//! entry.or_insert(10);
26//! assert_eq!(mule_map.get(5), Some(&10));
27//!
28//! // Using NonZero and bump
29//! let mut mule_map_non_zero = MuleMap::<u32, NonZero<i32>, Hash>::default();
30//!
31//! mule_map_non_zero.bump_non_zero(10);
32//! mule_map_non_zero.bump_non_zero(10);
33//! mule_map_non_zero.bump_non_zero(999_999);
34//! mule_map_non_zero.bump_non_zero(999_999);
35//!
36//! assert_eq!(mule_map_non_zero.get(10), NonZero::<i32>::new(2).as_ref());
37//! assert_eq!(mule_map_non_zero.get(999_999),NonZero::<i32>::new(2).as_ref());
38//! ```
39//!
40//! ## Highlights
41//!
42//!  - All primitive integer types are supported for keys (`u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, and `isize`).
43//!  - All corresponding `NonZero<T>` types are supported for keys.
44//!  - `NonZero<T>` key types take advantage of the niche optimizations (guaranteed by the rust compiler) by being stored as an `Option<NonZero<T>>`. This is used by `bump_non_zero()` to directly cast `Option<NonZero<T>>` to it's underlying integer type (using bytemuck - **no unsafe code**) and directly incrementing its value. See [benchmarks](#benchmarks) for details.
45//!  - *NOTE*: Currently the type of a const generic can't depend on another generic type argument, so `TABLE_MIN_VALUE` can't use the same type as the key. Because of this, I am using `i128`, but that means we can't represent values near `u128::MAX`. Hopefully having frequent keys near `u128::MAX` is extremely rare.
46//!  - No unsafe code used in safe APIs.
47//!
48//! ## <a name="benchmarks"></a> Benchmarks
49//!
50//! ### Benchmark Setup
51//!
52//! - Takes 2 random uniform distributions of small and large keys, and counts the frequency of all of the keys.
53//! - `MuleMap` stores the small keys (near 0) in its lookup table.
54//! - The benchmarks are run with and without shuffling the 2 random distributions of keys.
55//!   - If you expect your lookup table keys to appear in clumps, than the "No Shuffle" graph is more representative of your use case.
56//!   - If you don't expect runs of small keys (random order), than the graph with the keys shuffled is more representative of your use case.
57//! - "Input" is the percentage of small keys using the lookup table vs large keys that use the `HashMap`.
58//! - Benchmarks were run on a `MacBook` Pro 15-inch, Mid 2015 - 2.8 GHz Quad-Core Intel Core i7 (Sonoma). Both `MuleMap` `HashMap` are using `fnv_rs::FnvBuildHasher`. I tried other hash functions like [GxHash](https://github.com/ogxd/gxhash?tab=readme-ov-file#throughput), but they were slower (likely because my older CPU has slower AES/SSE2 instructions than more modern CPUs).
59//!
60//! ### Types of Maps Compared
61//!
62//! - **Hand Rolled** - Simple loop with an `if` block that switches between a `HashMap` and indexing into a lookup table. This is the baseline to show that `MuleMap` is a zero cost abstraction.
63//! - **`HashMap`** - Uses `HashMap<u32, usize, fnv_rs::FnvBuildHasher>`
64//! - **`MuleMap`** -  Uses `MuleMap<u32, usize, fnv_rs::FnvBuildHasher>`
65//! - **`MuleMap (NonZero)`** -  Uses `MuleMap<u32, NonZero<usize>, fnv_rs::FnvBuildHasher>`. This take advantage of the niche optimizations by directly casting `Option<NonZero<usize>>` to `usize` using bytemuck (**no unsafe code**)
66//!
67//! 
68//! 
69//!
70//! ## License
71//!
72//! Licensed under either of:
73//!
74//!  * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0))
75//!  * MIT license ([LICENSE-MIT](LICENSE-MIT) or [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT))
76//!
77//! at your option.
78//!
79
80pub use crate::mule_map::entry::*;
81pub use crate::mule_map::iterators::*;
82pub use crate::mule_map::key::Key;
83pub use crate::mule_map::*;
84
85mod mule_map;