├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── crates └── test-utils │ ├── Cargo.toml │ ├── benches │ └── benches.rs │ └── src │ └── lib.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── four_way.rs │ └── two_way.rs └── src ├── capacity.rs ├── entry.rs ├── indices.rs ├── iter.rs ├── lib.rs ├── replacement.rs └── replacement └── lru.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | features: ["--no-default-features", "--all-features"] 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Run tests 21 | run: cargo test --all --verbose ${{ matrix.features }} 22 | fuzz: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | fuzz_target: ["two_way", "four_way"] 27 | steps: 28 | - uses: actions/checkout@v4 29 | - run: rustup install nightly 30 | name: Install nightly Rust 31 | - run: cargo install cargo-fuzz 32 | name: Install `cargo fuzz` 33 | - run: cargo fuzz --version 34 | name: Query `cargo fuzz` version 35 | - run: cargo +nightly fuzz run ${{ matrix.fuzz_target }} -- -max_total_time=300 -rss_limit_mb=4096 36 | name: Run `cargo fuzz` 37 | check_benches: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | - run: cargo check --all --benches 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | # Created by criterion 6 | crates/test-utils/target 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | categories = ["memory-management", "caching", "data-structures"] 4 | description = "A generic N-way associative cache with fixed-size capacity and random or least recently used (LRU) replacement." 5 | documentation = "https://docs.rs/associative-cache" 6 | edition = "2018" 7 | keywords = ["direct-mapped", "associative", "lru", "cache"] 8 | license = "MIT OR Apache-2.0" 9 | name = "associative-cache" 10 | readme = "./README.md" 11 | repository = "https://github.com/fitzgen/associative-cache" 12 | version = "2.0.0" 13 | rust-version = "1.65" 14 | 15 | [package.metadata.docs.rs] 16 | all-features = true 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | 20 | [dependencies] 21 | rand = { version = "0.8.5", optional = true } 22 | 23 | [profile.bench] 24 | debug = true 25 | 26 | [workspace] 27 | members = [ 28 | ".", 29 | "crates/test-utils", 30 | ] 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `associative_cache` 2 | 3 | **A generic, fixed-size, associative cache data structure mapping `K` keys to 4 | `V` values.** 5 | 6 | [![](https://docs.rs/associative-cache/badge.svg)](https://docs.rs/associative-cache/) 7 | [![](https://img.shields.io/crates/v/associative-cache.svg)](https://crates.io/crates/associative-cache) 8 | [![](https://img.shields.io/crates/d/associative-cache.svg)](https://crates.io/crates/associative-cache) 9 | [![](https://github.com/fitzgen/associative-cache/actions/workflows/rust.yml/badge.svg)](https://github.com/fitzgen/associative-cache/actions/workflows/rust.yml) 10 | 11 | ## Capacity 12 | 13 | The cache has a constant, fixed-size capacity which is controlled by the `C` 14 | type parameter and the `Capacity` trait. The memory for the cache entries is 15 | eagerly allocated once and never resized. 16 | 17 | ## Associativity 18 | 19 | The cache can be configured as direct-mapped, two-way associative, four-way 20 | associative, etc... via the `I` type parameter and `Indices` trait. 21 | 22 | ## Replacement Policy 23 | 24 | The cache can be configured to replace the least recently used (LRU) entry, or a 25 | random entry via the `R` type parameter and the `Replacement` trait. 26 | 27 | ## Examples 28 | 29 | ```rust 30 | use associative_cache::*; 31 | 32 | // A two-way associative cache with random replacement mapping 33 | // `String`s to `usize`s. 34 | let cache = AssociativeCache::< 35 | String, 36 | usize, 37 | Capacity512, 38 | HashTwoWay, 39 | RandomReplacement 40 | >::default(); 41 | 42 | // A four-way associative cache with random replacement mapping 43 | // `*mut usize`s to `Vec`s. 44 | let cache = AssociativeCache::< 45 | *mut usize, 46 | Vec, 47 | Capacity32, 48 | PointerFourWay, 49 | RandomReplacement 50 | >::default(); 51 | 52 | // An eight-way associative, least recently used (LRU) cache mapping 53 | // `std::path::PathBuf`s to `std::fs::File`s. 54 | let cache = AssociativeCache::< 55 | std::path::PathBuf, 56 | WithLruTimestamp, 57 | Capacity128, 58 | HashEightWay, 59 | LruReplacement, 60 | >::default(); 61 | ``` 62 | -------------------------------------------------------------------------------- /crates/test-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "associative-cache-test-utils" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | associative-cache = { path = "../..", features = ["rand"] } 11 | quickcheck = "1.0.3" 12 | rand = "0.8.5" 13 | 14 | [dev-dependencies] 15 | criterion = "0.4.0" 16 | 17 | [[bench]] 18 | name = "benches" 19 | harness = false 20 | -------------------------------------------------------------------------------- /crates/test-utils/benches/benches.rs: -------------------------------------------------------------------------------- 1 | use associative_cache::*; 2 | use criterion::*; 3 | 4 | fn run_bench>(c: &mut Criterion, name: &str) { 5 | let elems = C::CAPACITY; 6 | 7 | { 8 | let mut group = c.benchmark_group("Insertion"); 9 | 10 | group.bench_function(name, |b| { 11 | let mut cache = 12 | AssociativeCache::<*mut u64, usize, C, I, RoundRobinReplacement>::default(); 13 | let mut iter = (0..elems) 14 | .cycle() 15 | .map(|i| (i * std::mem::align_of::()) as *mut u64); 16 | b.iter(|| { 17 | let i = iter.next().unwrap(); 18 | let key = black_box(i); 19 | let val = black_box(i as usize); 20 | black_box(cache.insert(key, val)); 21 | }) 22 | }); 23 | } 24 | 25 | { 26 | let mut group = c.benchmark_group("Query"); 27 | 28 | group.bench_function(name, |b| { 29 | let mut cache = 30 | AssociativeCache::<*mut u64, usize, C, I, RoundRobinReplacement>::default(); 31 | 32 | for i in 0..elems { 33 | // Make the cache have a mix of existing and missing entries. 34 | if i % 2 == 0 { 35 | cache.insert((i * std::mem::align_of::()) as *mut u64, i); 36 | } 37 | } 38 | 39 | let mut iter = (0..elems) 40 | .cycle() 41 | .map(|i| (i * std::mem::align_of::()) as *mut u64); 42 | 43 | b.iter(|| { 44 | let i = iter.next().unwrap(); 45 | let key = black_box(i); 46 | black_box(cache.get(&key)); 47 | }) 48 | }); 49 | } 50 | } 51 | 52 | macro_rules! define_benches { 53 | ( $( $name:ident ( $cap:ident, $ind:ident ); )* ) => { 54 | $( 55 | fn $name(c: &mut Criterion) { 56 | run_bench::<$cap, $ind>(c, concat!(stringify!($ind), "-", stringify!($cap))); 57 | } 58 | )* 59 | 60 | criterion_group!(benches $( , $name )* ); 61 | } 62 | } 63 | 64 | define_benches! { 65 | hash_direct_mapped_512(Capacity512, HashDirectMapped); 66 | hash_two_way_512(Capacity512, HashTwoWay); 67 | hash_four_way_512(Capacity512, HashFourWay); 68 | hash_eight_way_512(Capacity512, HashEightWay); 69 | hash_sixteen_way_512(Capacity512, HashSixteenWay); 70 | hash_thirty_two_way_512(Capacity512, HashThirtyTwoWay); 71 | 72 | pointer_direct_mapped_512(Capacity512, PointerDirectMapped); 73 | pointer_two_way_512(Capacity512, PointerTwoWay); 74 | pointer_four_way_512(Capacity512, PointerFourWay); 75 | pointer_eight_way_512(Capacity512, PointerEightWay); 76 | pointer_sixteen_way_512(Capacity512, PointerSixteenWay); 77 | pointer_thirty_two_way_512(Capacity512, PointerThirtyTwoWay); 78 | } 79 | 80 | criterion_main!(benches); 81 | -------------------------------------------------------------------------------- /crates/test-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use associative_cache::*; 2 | 3 | use quickcheck::{Arbitrary, Gen}; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Clone, Debug)] 7 | pub enum MethodCall { 8 | Insert, 9 | Remove, 10 | } 11 | 12 | impl Arbitrary for MethodCall { 13 | fn arbitrary(g: &mut Gen) -> Self { 14 | match g.choose(&[0, 1]).unwrap() { 15 | 0 => MethodCall::Insert, 16 | 1 => MethodCall::Remove, 17 | _ => unreachable!(), 18 | } 19 | } 20 | } 21 | 22 | #[derive(Clone, Debug)] 23 | pub struct MethodCalls { 24 | calls: Vec, 25 | entries: Vec, 26 | } 27 | 28 | // NB: Entry contains a `*mut u64` but we never deref it, its just there to be 29 | // able to test `Pointer*Way`. 30 | unsafe impl Send for MethodCalls {} 31 | 32 | impl Arbitrary for MethodCalls { 33 | fn arbitrary(g: &mut Gen) -> Self { 34 | let calls: Vec = Arbitrary::arbitrary(g); 35 | 36 | let entries: HashMap = Arbitrary::arbitrary(g); 37 | let entries: Vec = entries 38 | .into_iter() 39 | .map(>::from) 40 | .collect(); 41 | 42 | MethodCalls { calls, entries } 43 | } 44 | 45 | fn shrink(&self) -> Box> { 46 | let og_calls = self.calls.clone(); 47 | let og_entries = self.entries.clone(); 48 | 49 | let shrink_calls = self.calls.shrink(); 50 | let shrink_entries = self 51 | .entries 52 | .iter() 53 | .map(|e| (e.key as usize / std::mem::align_of::(), e.val)) 54 | .collect::>() 55 | .shrink() 56 | .map(|vs| { 57 | vs.into_iter() 58 | .map(>::from) 59 | .collect::>() 60 | }); 61 | 62 | let shrinks = shrink_calls 63 | .zip(shrink_entries) 64 | .flat_map(move |(calls, entries)| { 65 | vec![ 66 | MethodCalls { 67 | calls: calls.clone(), 68 | entries: entries.clone(), 69 | }, 70 | MethodCalls { 71 | calls: og_calls.clone(), 72 | entries, 73 | }, 74 | MethodCalls { 75 | calls, 76 | entries: og_entries.clone(), 77 | }, 78 | ] 79 | }); 80 | 81 | Box::new(shrinks) as _ 82 | } 83 | } 84 | 85 | macro_rules! bail { 86 | ( $($args:expr),* ) => { 87 | { 88 | let msg = format!($($args),*); 89 | eprintln!("error: {}", msg); 90 | return Err(msg); 91 | } 92 | } 93 | } 94 | 95 | impl MethodCalls { 96 | pub fn run(self) -> Result<(), String> 97 | where 98 | C: Capacity, 99 | I: Indices<*mut u64, C>, 100 | R: Replacement + Default, 101 | { 102 | let MethodCalls { calls, entries } = self; 103 | let mut cache = AssociativeCache::<*mut u64, usize, C, I, R>::default(); 104 | let mut expected = HashMap::<*mut u64, usize>::new(); 105 | 106 | for (method, entry) in calls.into_iter().zip(entries.into_iter()) { 107 | if cache.len() != expected.len() { 108 | bail!("cache length mismatch"); 109 | } 110 | 111 | for (expected_key, expected_value) in &expected { 112 | match cache.get(expected_key) { 113 | Some(v) if v == expected_value => continue, 114 | otherwise => bail!( 115 | "expected {:?}; found {:?}", 116 | (expected_key, expected_value), 117 | otherwise 118 | ), 119 | } 120 | } 121 | 122 | for (actual_key, actual_value) in cache.iter() { 123 | match expected.get(actual_key) { 124 | Some(v) if v == actual_value => continue, 125 | otherwise => bail!( 126 | "expected {:?}; found {:?}", 127 | otherwise, 128 | (actual_key, actual_value) 129 | ), 130 | } 131 | } 132 | 133 | match method { 134 | MethodCall::Insert => { 135 | match ( 136 | cache.insert(entry.key, entry.val), 137 | expected.insert(entry.key, entry.val), 138 | ) { 139 | (None, None) => continue, 140 | (Some((k, v)), Some(val)) if k == entry.key && v == val => continue, 141 | (Some((k, v)), None) => { 142 | if k != entry.key && Some(v) == expected.remove(&k) { 143 | continue; 144 | } 145 | bail!("replaced unknown entry on insert: {:?}", (k, v)); 146 | } 147 | otherwise => { 148 | bail!("cache mismatch on insert: {:?}", otherwise); 149 | } 150 | } 151 | } 152 | MethodCall::Remove => { 153 | match (cache.remove(&entry.key), expected.remove(&entry.key)) { 154 | (Some(v), Some(val)) if v == val => continue, 155 | (None, None) => continue, 156 | otherwise => { 157 | bail!("cache mismatch on delete: {:?}", otherwise); 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | Ok(()) 165 | } 166 | } 167 | 168 | #[derive(Clone, Copy, Debug)] 169 | pub struct Entry { 170 | pub key: *mut u64, 171 | pub val: usize, 172 | } 173 | 174 | impl From<(usize, usize)> for Entry { 175 | fn from((key, val): (usize, usize)) -> Self { 176 | let key = key.wrapping_mul(std::mem::align_of::()) as *mut u64; 177 | Entry { key, val } 178 | } 179 | } 180 | 181 | #[cfg(test)] 182 | mod quickchecks { 183 | use super::*; 184 | use quickcheck::quickcheck; 185 | 186 | quickcheck! { 187 | fn test_pointer_two_way(test: MethodCalls) -> Result<(), String> { 188 | test.run::() 189 | } 190 | 191 | fn test_pointer_four_way(test: MethodCalls) -> Result<(), String> { 192 | test.run::() 193 | } 194 | 195 | fn test_hash_two_way(test: MethodCalls) -> Result<(), String> { 196 | test.run::() 197 | } 198 | 199 | fn test_hash_four_way(test: MethodCalls) -> Result<(), String> { 200 | test.run::() 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "associative-cache-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2018" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | bufrng = "1" 13 | quickcheck = "0.9" 14 | 15 | [dependencies.associative-cache-test-utils] 16 | path = "../crates/test-utils" 17 | 18 | [dependencies.libfuzzer-sys] 19 | git = "https://github.com/rust-fuzz/libfuzzer-sys.git" 20 | 21 | # Prevent this from interfering with workspaces 22 | [workspace] 23 | members = ["."] 24 | 25 | [[bin]] 26 | name = "two_way" 27 | path = "fuzz_targets/two_way.rs" 28 | 29 | [[bin]] 30 | name = "four_way" 31 | path = "fuzz_targets/four_way.rs" 32 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/four_way.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use associative_cache_test_utils::*; 4 | use bufrng::BufRng; 5 | use libfuzzer_sys::fuzz_target; 6 | use quickcheck::Arbitrary; 7 | 8 | fuzz_target!(|data: &[u8]| { 9 | let mut gen = quickcheck::StdGen::new(BufRng::new(data), std::cmp::max(data.len(), 1)); 10 | let test = MethodCalls::arbitrary(&mut gen); 11 | if let Err(e) = test.run::() { 12 | panic!("error: {}", e); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/two_way.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use associative_cache_test_utils::*; 4 | use bufrng::BufRng; 5 | use libfuzzer_sys::fuzz_target; 6 | use quickcheck::Arbitrary; 7 | 8 | fuzz_target!(|data: &[u8]| { 9 | let mut gen = quickcheck::StdGen::new(BufRng::new(data), std::cmp::max(data.len(), 1)); 10 | let test = MethodCalls::arbitrary(&mut gen); 11 | if let Err(e) = test.run::() { 12 | panic!("error: {}", e); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /src/capacity.rs: -------------------------------------------------------------------------------- 1 | //! Constant cache capacity implementations. 2 | 3 | use super::Capacity; 4 | 5 | macro_rules! define_capacity { 6 | ( $( $(#[$attr:meta])* $name:ident => $n:expr; )* ) => { 7 | $( 8 | $( #[$attr] )* 9 | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 10 | pub struct $name; 11 | 12 | impl Capacity for $name { 13 | const CAPACITY: usize = $n; 14 | } 15 | )* 16 | } 17 | } 18 | 19 | define_capacity! { 20 | /// Constant cache capacity = 1. 21 | Capacity1 => 1; 22 | /// Constant cache capacity = 2. 23 | Capacity2 => 2; 24 | /// Constant cache capacity = 4. 25 | Capacity4 => 4; 26 | /// Constant cache capacity = 8. 27 | Capacity8 => 8; 28 | /// Constant cache capacity = 16. 29 | Capacity16 => 16; 30 | /// Constant cache capacity = 32. 31 | Capacity32 => 32; 32 | /// Constant cache capacity = 64. 33 | Capacity64 => 64; 34 | /// Constant cache capacity = 128. 35 | Capacity128 => 128; 36 | /// Constant cache capacity = 256. 37 | Capacity256 => 256; 38 | /// Constant cache capacity = 512. 39 | Capacity512 => 512; 40 | /// Constant cache capacity = 1024. 41 | Capacity1024 => 1024; 42 | /// Constant cache capacity = 2048. 43 | Capacity2048 => 2048; 44 | /// Constant cache capacity = 4096. 45 | Capacity4096 => 4096; 46 | /// Constant cache capacity = 8192. 47 | Capacity8192 => 8192; 48 | } 49 | -------------------------------------------------------------------------------- /src/entry.rs: -------------------------------------------------------------------------------- 1 | //! An API for get-or-create operations on cache entries, similar to 2 | //! `std::collections::HashMap`'s entry API. 3 | 4 | use super::*; 5 | use std::fmt; 6 | 7 | /// A potentially-empty entry in a cache, used to perform get-or-create 8 | /// operations on the cache. 9 | /// 10 | /// Constructed via the `AssociativeCache::entry` method. 11 | pub struct Entry<'a, K, V, C, I, R> 12 | where 13 | C: Capacity, 14 | R: Replacement, 15 | { 16 | pub(crate) cache: &'a mut AssociativeCache, 17 | pub(crate) index: usize, 18 | pub(crate) kind: EntryKind, 19 | } 20 | 21 | impl<'a, K, V, C, I, R> fmt::Debug for Entry<'a, K, V, C, I, R> 22 | where 23 | C: Capacity, 24 | R: Replacement, 25 | { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | let Entry { 28 | cache: _, 29 | ref index, 30 | ref kind, 31 | } = self; 32 | f.debug_struct("Entry") 33 | .field("index", index) 34 | .field("kind", kind) 35 | .finish() 36 | } 37 | } 38 | 39 | #[derive(Debug)] 40 | pub(crate) enum EntryKind { 41 | // The index is occupied with a cache entry for this key. 42 | Occupied, 43 | // The index is for a slot that has no entry in it. 44 | Vacant, 45 | // The index is for a slot that has a to-be-replaced entry for a 46 | // different key. 47 | Replace, 48 | } 49 | 50 | impl<'a, K, V, C, I, R> Entry<'a, K, V, C, I, R> 51 | where 52 | C: Capacity, 53 | I: Indices, 54 | R: Replacement, 55 | { 56 | /// Get the underlying cached data, creating and inserting it into the cache 57 | /// if it doesn't already exist. 58 | /// 59 | /// ## Differences from `std::collections::HashMap`'s `Entry` API 60 | /// 61 | /// `std::collections::HashMap`'s `Entry` API takes unconditional ownership 62 | /// of the query key, even in scenarios where there is already an entry with 63 | /// that key in the map. This means that if your keys are expensive to 64 | /// create (like `String` and its heap allocation) that you have to eagerly 65 | /// construct the key even if you don't end up needing it. 66 | /// 67 | /// In contrast, the `associative_cache::Entry` API allows you to get an 68 | /// `Entry` with just a borrow of a key, allowing you to delay the 69 | /// potentially-expensive key construction until we actually need 70 | /// it. However, this is not without drawbacks. Now the `or_insert_with` 71 | /// method needs a way to construct an owned key: the `make_key` parameter 72 | /// here. **`make_key` must return an owned key that is equivalent to the 73 | /// borrowed key that was used to get this `Entry`.** Failure to do this 74 | /// will result in an invalid cache (likely manifesting as wasted entries 75 | /// that take up space but can't ever be queried for). 76 | /// 77 | /// # Example 78 | /// 79 | /// ``` 80 | /// use associative_cache::*; 81 | /// 82 | /// let mut cache = AssociativeCache::< 83 | /// String, 84 | /// usize, 85 | /// Capacity4, 86 | /// HashTwoWay, 87 | /// RoundRobinReplacement, 88 | /// >::default(); 89 | /// 90 | /// // Get or create an entry for "hi", delaying the `&str` to `String` 91 | /// // allocation until if/when we actually insert into the cache. 92 | /// let val = cache.entry("hi").or_insert_with( 93 | /// || "hi".to_string(), 94 | /// || 42, 95 | /// ); 96 | /// 97 | /// // The cache was empty, so we inserted the default value of 42. 98 | /// assert_eq!(*val, 42); 99 | /// 100 | /// // We can modify the value. 101 | /// *val += 1; 102 | /// ``` 103 | #[inline] 104 | pub fn or_insert_with( 105 | self, 106 | make_key: impl FnOnce() -> K, 107 | make_val: impl FnOnce() -> V, 108 | ) -> &'a mut V { 109 | assert!(self.index < C::CAPACITY); 110 | match self.kind { 111 | EntryKind::Occupied => match &mut self.cache.entries[self.index] { 112 | Some((_, v)) => v, 113 | _ => unreachable!(), 114 | }, 115 | EntryKind::Vacant | EntryKind::Replace => { 116 | if let EntryKind::Vacant = self.kind { 117 | self.cache.len += 1; 118 | } 119 | self.cache.entries[self.index] = Some((make_key(), make_val())); 120 | match &mut self.cache.entries[self.index] { 121 | Some((_, v)) => { 122 | self.cache.replacement_policy.on_insert(v); 123 | v 124 | } 125 | _ => unreachable!(), 126 | } 127 | } 128 | } 129 | } 130 | 131 | /// If inserting into this `Entry` will replace another entry in the 132 | /// cache, remove that other entry from the cache and return it now. 133 | /// 134 | /// # Example 135 | /// 136 | /// ``` 137 | /// use associative_cache::*; 138 | /// 139 | /// let mut cache = AssociativeCache::< 140 | /// String, 141 | /// usize, 142 | /// Capacity256, 143 | /// HashTwoWay, 144 | /// RoundRobinReplacement, 145 | /// >::default(); 146 | /// 147 | /// cache.insert("hi".to_string(), 5); 148 | /// 149 | /// let mut entry = cache.entry("bye"); 150 | /// 151 | /// // Because this entry could replace the entry for "hi" depending on the hash 152 | /// // function in use, we have an opportunity to recover the 153 | /// // about-to-be-replaced entry here. 154 | /// if let Some((key, val)) = entry.take_entry_that_will_be_replaced() { 155 | /// assert_eq!(key, "hi"); 156 | /// assert_eq!(val, 5); 157 | /// } 158 | /// 159 | /// let val = entry.or_insert_with(|| "bye".into(), || 1337); 160 | /// assert_eq!(*val, 1337); 161 | /// ``` 162 | #[inline] 163 | pub fn take_entry_that_will_be_replaced(&mut self) -> Option<(K, V)> { 164 | assert!(self.index < C::CAPACITY); 165 | if let EntryKind::Replace = self.kind { 166 | self.cache.len -= 1; 167 | self.kind = EntryKind::Vacant; 168 | mem::replace(&mut self.cache.entries[self.index], None) 169 | } else { 170 | None 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/indices.rs: -------------------------------------------------------------------------------- 1 | //! Various kinds of associativity and `Indices` implementations. 2 | 3 | use super::{Capacity, Indices}; 4 | use std::collections::hash_map::DefaultHasher; 5 | use std::hash::{Hash, Hasher}; 6 | use std::marker::PhantomData; 7 | use std::ops::Range; 8 | 9 | #[inline] 10 | fn hash_to_usize(mut hasher: impl Hasher, h: &H) -> usize 11 | where 12 | H: ?Sized + Hash, 13 | { 14 | h.hash(&mut hasher); 15 | hasher.finish() as usize 16 | } 17 | 18 | macro_rules! define_hash_n_way { 19 | ( $( $( #[$attr:meta] )* $name:ident => $n:expr; )* ) => { $( 20 | $( #[ $attr ] )* 21 | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 22 | pub struct $name { 23 | _hasher: PhantomData, 24 | } 25 | 26 | impl Indices for $name 27 | where 28 | T: ?Sized + Hash, 29 | C: Capacity, 30 | H: Hasher + Default, 31 | { 32 | type Indices = Range; 33 | 34 | #[inline] 35 | fn indices(key: &T) -> Self::Indices { 36 | assert!(C::CAPACITY >= $n); 37 | let hasher = H::default(); 38 | let base = hash_to_usize(hasher, key) % (C::CAPACITY / $n) * $n; 39 | base..base + $n 40 | } 41 | } 42 | )* } 43 | } 44 | 45 | define_hash_n_way! { 46 | /// Direct-mapped (i.e. one-way associative) caching based on the key's 47 | /// `Hash` implementation. 48 | /// 49 | /// See the `Indices` trait's documentation for more on associativity. 50 | HashDirectMapped => 1; 51 | /// Two-way set associative caching based on the key's `Hash` 52 | /// implementation. 53 | /// 54 | /// See the `Indices` trait's documentation for more on associativity. 55 | HashTwoWay => 2; 56 | /// Four-way set associative caching based on the key's `Hash` 57 | /// implementation. 58 | /// 59 | /// See the `Indices` trait's documentation for more on associativity. 60 | HashFourWay => 4; 61 | /// Eight-way set associative caching based on the key's `Hash` 62 | /// implementation. 63 | /// 64 | /// See the `Indices` trait's documentation for more on associativity. 65 | HashEightWay => 8; 66 | /// Sixteen-way set associative caching based on the key's `Hash` 67 | /// implementation. 68 | /// 69 | /// See the `Indices` trait's documentation for more on associativity. 70 | HashSixteenWay => 16; 71 | /// 32-way set associative caching based on the key's `Hash` implementation. 72 | /// 73 | /// See the `Indices` trait's documentation for more on associativity. 74 | HashThirtyTwoWay => 32; 75 | } 76 | 77 | macro_rules! define_pointer_n_way { 78 | ( $( $( #[$attr:meta] )* $name: ident => $n:expr; )* ) => { 79 | $( 80 | $( #[$attr] )* 81 | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 82 | pub struct $name; 83 | 84 | impl Indices<*mut T, C> for $name 85 | where 86 | C: Capacity 87 | { 88 | type Indices = Range; 89 | 90 | #[inline] 91 | fn indices(&ptr: &*mut T) -> Self::Indices { 92 | assert!(C::CAPACITY >= $n); 93 | 94 | let ptr = ptr as usize; 95 | 96 | // The bottom bits of the pointer are all zero because of 97 | // alignment, so get rid of them. The compiler should be 98 | // able to clean up this divide into a right shift because 99 | // of the constant, power-of-two divisor. 100 | let i = ptr / std::mem::align_of::(); 101 | 102 | let base = i % (C::CAPACITY / $n) * $n; 103 | base..(base + $n) 104 | } 105 | } 106 | 107 | impl Indices<*const T, C> for $name 108 | where 109 | C: Capacity 110 | { 111 | type Indices = >::Indices; 112 | 113 | #[inline] 114 | fn indices(&ptr: &*const T) -> Self::Indices { 115 | >::indices(&(ptr as *mut T)) 116 | } 117 | } 118 | )* 119 | }; 120 | } 121 | 122 | define_pointer_n_way! { 123 | /// Direct-mapped (i.e. one-way associative) caching based on the key's 124 | /// pointer value. 125 | /// 126 | /// See the `Indices` trait's documentation for more on associativity. 127 | PointerDirectMapped => 1; 128 | /// Two-way set associative caching based on the key's pointer value. 129 | /// 130 | /// See the `Indices` trait's documentation for more on associativity. 131 | PointerTwoWay => 2; 132 | /// Four-way set associative caching based on the key's pointer value. 133 | /// 134 | /// See the `Indices` trait's documentation for more on associativity. 135 | PointerFourWay => 4; 136 | /// Eight-way set associative caching based on the key's pointer value. 137 | /// 138 | /// See the `Indices` trait's documentation for more on associativity. 139 | PointerEightWay => 8; 140 | /// Sixteen-way set associative caching based on the key's pointer value. 141 | /// 142 | /// See the `Indices` trait's documentation for more on associativity. 143 | PointerSixteenWay => 16; 144 | /// 32-way set associative caching based on the key's pointer value. 145 | /// 146 | /// See the `Indices` trait's documentation for more on associativity. 147 | PointerThirtyTwoWay => 32; 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use super::*; 153 | use crate::Capacity4; 154 | 155 | #[test] 156 | fn pointer_direct_mapped() { 157 | assert_eq!( 158 | >::indices(&(0 as *mut u64)), 159 | 0..1 160 | ); 161 | assert_eq!( 162 | >::indices(&(8 as *mut u64)), 163 | 1..2 164 | ); 165 | assert_eq!( 166 | >::indices(&(16 as *mut u64)), 167 | 2..3 168 | ); 169 | assert_eq!( 170 | >::indices(&(24 as *mut u64)), 171 | 3..4 172 | ); 173 | assert_eq!( 174 | >::indices(&(32 as *mut u64)), 175 | 0..1 176 | ); 177 | } 178 | 179 | #[test] 180 | fn pointer_two_way() { 181 | assert_eq!( 182 | >::indices(&(0 as *mut u64)), 183 | 0..2 184 | ); 185 | assert_eq!( 186 | >::indices(&(8 as *mut u64)), 187 | 2..4 188 | ); 189 | assert_eq!( 190 | >::indices(&(16 as *mut u64)), 191 | 0..2 192 | ); 193 | assert_eq!( 194 | >::indices(&(24 as *mut u64)), 195 | 2..4 196 | ); 197 | assert_eq!( 198 | >::indices(&(32 as *mut u64)), 199 | 0..2 200 | ); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | //! Various iterator implementations and type definitions for 2 | //! `AssociativeCache`. 3 | 4 | use super::*; 5 | 6 | impl<'a, K, V, C, I, R> IntoIterator for &'a AssociativeCache 7 | where 8 | C: Capacity, 9 | R: Replacement, 10 | { 11 | type Item = (&'a K, &'a V); 12 | type IntoIter = Iter<'a, K, V>; 13 | 14 | #[inline] 15 | fn into_iter(self) -> Self::IntoIter { 16 | Iter { 17 | len: self.len(), 18 | inner: self.entries.iter(), 19 | } 20 | } 21 | } 22 | 23 | impl<'a, K, V, C, I, R> IntoIterator for &'a mut AssociativeCache 24 | where 25 | C: Capacity, 26 | R: Replacement, 27 | { 28 | type Item = (&'a K, &'a mut V); 29 | type IntoIter = IterMut<'a, K, V>; 30 | 31 | #[inline] 32 | fn into_iter(self) -> Self::IntoIter { 33 | IterMut { 34 | len: self.len(), 35 | inner: self.entries.iter_mut(), 36 | } 37 | } 38 | } 39 | 40 | impl IntoIterator for AssociativeCache 41 | where 42 | C: Capacity, 43 | R: Replacement, 44 | { 45 | type Item = (K, V); 46 | type IntoIter = IntoIter; 47 | 48 | #[inline] 49 | fn into_iter(self) -> Self::IntoIter { 50 | IntoIter { 51 | len: self.len(), 52 | inner: self.entries.into_iter(), 53 | } 54 | } 55 | } 56 | 57 | /// An iterator over shared borrows of the cache keys and values. 58 | /// 59 | /// See `AssociativeCache::iter` for details. 60 | #[derive(Debug)] 61 | pub struct Iter<'a, K, V> { 62 | len: usize, 63 | inner: std::slice::Iter<'a, Option<(K, V)>>, 64 | } 65 | 66 | impl<'a, K, V> Iterator for Iter<'a, K, V> { 67 | type Item = (&'a K, &'a V); 68 | 69 | #[inline] 70 | fn next(&mut self) -> Option { 71 | loop { 72 | match self.inner.next() { 73 | None => return None, 74 | Some(None) => continue, 75 | Some(Some((k, v))) => { 76 | debug_assert!(self.len > 0); 77 | self.len -= 1; 78 | return Some((k, v)); 79 | } 80 | } 81 | } 82 | } 83 | 84 | #[inline] 85 | fn size_hint(&self) -> (usize, Option) { 86 | (self.len, Some(self.len)) 87 | } 88 | } 89 | 90 | impl ExactSizeIterator for Iter<'_, K, V> {} 91 | 92 | /// An iterator over shared borrows of the cache keys and mutable borrows of the 93 | /// cache values. 94 | /// 95 | /// See `AssociativeCache::iter_mut` for details. 96 | #[derive(Debug)] 97 | pub struct IterMut<'a, K, V> { 98 | len: usize, 99 | inner: std::slice::IterMut<'a, Option<(K, V)>>, 100 | } 101 | 102 | impl<'a, K, V> Iterator for IterMut<'a, K, V> { 103 | type Item = (&'a K, &'a mut V); 104 | 105 | #[inline] 106 | fn next(&mut self) -> Option { 107 | loop { 108 | match self.inner.next() { 109 | None => return None, 110 | Some(None) => continue, 111 | Some(Some((k, v))) => { 112 | debug_assert!(self.len > 0); 113 | self.len -= 1; 114 | return Some((k, v)); 115 | } 116 | } 117 | } 118 | } 119 | 120 | #[inline] 121 | fn size_hint(&self) -> (usize, Option) { 122 | (self.len, Some(self.len)) 123 | } 124 | } 125 | 126 | impl ExactSizeIterator for IterMut<'_, K, V> {} 127 | 128 | /// An iterator that consumes and takes ownership of a cache's keys and values. 129 | /// 130 | /// See `AssociativeCache::into_iter` for details. 131 | #[derive(Debug)] 132 | pub struct IntoIter { 133 | len: usize, 134 | inner: std::vec::IntoIter>, 135 | } 136 | 137 | impl Iterator for IntoIter { 138 | type Item = (K, V); 139 | 140 | #[inline] 141 | fn next(&mut self) -> Option { 142 | loop { 143 | match self.inner.next() { 144 | None => return None, 145 | Some(None) => continue, 146 | Some(Some(x)) => { 147 | debug_assert!(self.len > 0); 148 | self.len -= 1; 149 | return Some(x); 150 | } 151 | } 152 | } 153 | } 154 | 155 | #[inline] 156 | fn size_hint(&self) -> (usize, Option) { 157 | (self.len, Some(self.len)) 158 | } 159 | } 160 | 161 | impl ExactSizeIterator for IntoIter {} 162 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a generic, fixed-size, N-way associative cache data 2 | //! structure that supports random and least recently used replacement (or your 3 | //! own custom algorithm). 4 | //! 5 | //! Dive into the documentation for [`AssociativeCache`] to begin. 6 | 7 | #![deny(missing_docs, missing_debug_implementations)] 8 | 9 | pub mod capacity; 10 | pub mod entry; 11 | pub mod indices; 12 | pub mod iter; 13 | pub mod replacement; 14 | 15 | pub use capacity::*; 16 | pub use entry::*; 17 | pub use indices::*; 18 | pub use iter::*; 19 | pub use replacement::*; 20 | 21 | use std::borrow::Borrow; 22 | use std::cmp::max; 23 | use std::marker::PhantomData; 24 | use std::mem; 25 | 26 | /// A constant cache capacity. 27 | /// 28 | /// ## Provided `Capacity` Implementations 29 | /// 30 | /// This crate defines all power-of-two capacities up to 8192 as 31 | /// `associative_cache::CapacityN`. 32 | /// 33 | /// ``` 34 | /// use associative_cache::Capacity256; 35 | /// ``` 36 | /// 37 | /// ## Defining Custom Cache Capacities 38 | /// 39 | /// You may implement this trait yourself to define your own custom cache 40 | /// capacities: 41 | /// 42 | /// ``` 43 | /// use associative_cache::Capacity; 44 | /// 45 | /// pub struct Capacity42; 46 | /// 47 | /// impl Capacity for Capacity42 { 48 | /// const CAPACITY: usize = 42; 49 | /// } 50 | /// ``` 51 | pub trait Capacity { 52 | /// The constant capacity for a cache. 53 | /// 54 | /// Must be greater than zero. 55 | const CAPACITY: usize; 56 | } 57 | 58 | /// Given a cache key, return all the slots within the cache where its entry 59 | /// might be. 60 | /// 61 | /// ## Associativity 62 | /// 63 | /// The associativity of a cache is how many slots in the cache a key might 64 | /// reside in. There are generally many more possible values than there is 65 | /// capacity in the cache. Allowing a entry to be in one of multiple slots 66 | /// within the cache raises the cache hit rate, but takes a little extra time 67 | /// when querying the cache because each of those multiple slots need to be 68 | /// considered. 69 | /// 70 | /// * **Direct-mapped:** A cache key corresponds to only one possible slot in 71 | /// the cache. 72 | /// 73 | /// * **Two-way:** A cache key corresponds to two possible slots in the cache. 74 | /// 75 | /// * **Four-way:** A cache key corresponds to four possible slots in the cache. 76 | /// 77 | /// * Etc... 78 | /// 79 | /// [Wikipedia has more details on cache 80 | /// associativity.](https://en.wikipedia.org/wiki/CPU_cache#Associativity) 81 | /// 82 | /// ## Provided Implementations 83 | /// 84 | /// This crate provides two flavors of associativity out of the box: 85 | /// 86 | /// 1. `Hash`-based implementations: `HashDirectMapped` and 87 | /// `Hash{Two,Four,Eight,Sixteen,ThirtyTwo}Way` provide various associativity 88 | /// levels based on the key's `Hash` implementation. 89 | /// 90 | /// 2. Pointer-based implementations: `PointerDirectMapped` and 91 | /// `Pointer{Two,Four,Eight,Sixteen,ThirtyTwo}Way` provide various 92 | /// associativity levels based on the pointer value, taking advantage of its 93 | /// referenced type's alignment. This will generally provide faster lookups 94 | /// than hashing, but is less general. 95 | /// 96 | /// ## Custom Implementation Requirements 97 | /// 98 | /// Implementations must be deterministic. 99 | /// 100 | /// All indices yielded must be within the capacity. 101 | /// 102 | /// The iterator must always be non-empty. 103 | /// 104 | /// For example, to implement a two-way cache, return an iterator of two 105 | /// indices. 106 | pub trait Indices 107 | where 108 | K: ?Sized, 109 | C: Capacity, 110 | { 111 | /// The iterator over indices within the range `0..C::CAPACITY` yielding the 112 | /// slots in the cache where the key's entry might reside. 113 | type Indices: ExactSizeIterator; 114 | 115 | /// Get the indices within the range `0..C::CAPACITY` representing slots in 116 | /// the cache where the given key's entry might reside. 117 | fn indices(key: &K) -> Self::Indices; 118 | } 119 | 120 | /// Given that we need to replace a cache entry when inserting a new one, consider 121 | /// each `(index, entry)` pair and return the index whose entry should be 122 | /// replaced. 123 | /// 124 | /// The given iterator will always be non-empty, and its indices will always be 125 | /// within the capacity, assuming the `Indices` that this is paired with is 126 | /// conformant. 127 | pub trait Replacement { 128 | /// Choose which of the given cache entries will be replaced. 129 | fn choose_for_replacement<'a>( 130 | &mut self, 131 | candidates: impl ExactSizeIterator, 132 | ) -> usize 133 | where 134 | V: 'a; 135 | 136 | /// Called whenever an existing cache entry is hit. 137 | fn on_hit(&self, value: &V) { 138 | let _ = value; 139 | } 140 | 141 | /// Called whenever a new cache entry is inserted. 142 | fn on_insert(&self, value: &V) { 143 | let _ = value; 144 | } 145 | } 146 | 147 | /// A fixed-size associative cache mapping `K` keys to `V` values. 148 | /// 149 | /// ## Capacity 150 | /// 151 | /// The cache has a constant, fixed-size capacity which is controlled by the `C` 152 | /// type parameter and the `Capacity` trait. The memory for the cache entries is 153 | /// eagerly allocated once and never resized. 154 | /// 155 | /// ## Associativity 156 | /// 157 | /// The cache can be configured as direct-mapped, two-way associative, four-way 158 | /// associative, etc... via the `I` type parameter and `Indices` trait. 159 | /// 160 | /// ## Replacement Policy 161 | /// 162 | /// Can be configured to replace the least-recently used entry, or a random 163 | /// entry via the `R` type parameter and the `Replacement` trait. 164 | /// 165 | /// ## Examples 166 | /// 167 | /// ``` 168 | /// # #[cfg(feature = "rand")] 169 | /// # { 170 | /// use associative_cache::*; 171 | /// 172 | /// // A two-way associative cache with random replacement mapping 173 | /// // `String`s to `usize`s. 174 | /// let cache = AssociativeCache::< 175 | /// String, 176 | /// usize, 177 | /// Capacity512, 178 | /// HashTwoWay, 179 | /// RandomReplacement 180 | /// >::default(); 181 | /// 182 | /// // A four-way associative cache with random replacement mapping 183 | /// // `*mut usize`s to `Vec`s. 184 | /// let cache = AssociativeCache::< 185 | /// *mut usize, 186 | /// Vec, 187 | /// Capacity32, 188 | /// PointerFourWay, 189 | /// RandomReplacement 190 | /// >::default(); 191 | /// 192 | /// // An eight-way associative, least recently used (LRU) cache mapping 193 | /// // `std::path::PathBuf`s to `std::fs::File`s. 194 | /// let cache = AssociativeCache::< 195 | /// std::path::PathBuf, 196 | /// WithLruTimestamp, 197 | /// Capacity128, 198 | /// HashEightWay, 199 | /// LruReplacement, 200 | /// >::default(); 201 | /// # } 202 | /// ``` 203 | #[derive(Debug)] 204 | pub struct AssociativeCache 205 | where 206 | C: Capacity, 207 | R: Replacement, 208 | { 209 | entries: Vec>, 210 | len: usize, 211 | replacement_policy: R, 212 | _capacity: PhantomData, 213 | _indices: PhantomData, 214 | } 215 | 216 | impl Default for AssociativeCache 217 | where 218 | C: Capacity, 219 | R: Default + Replacement, 220 | { 221 | fn default() -> Self { 222 | AssociativeCache::with_replacement_policy(R::default()) 223 | } 224 | } 225 | 226 | impl AssociativeCache 227 | where 228 | C: Capacity, 229 | R: Replacement, 230 | { 231 | /// Construct an `AssociativeCache` with the given replacement policy. 232 | /// 233 | /// ## Example 234 | /// 235 | /// ``` 236 | /// # #[cfg(feature = "rand")] 237 | /// # { 238 | /// use associative_cache::*; 239 | /// use rand::{rngs::StdRng, SeedableRng}; 240 | /// use std::path::PathBuf; 241 | /// use std::fs::File; 242 | /// 243 | /// // Note: `RandomReplacement` requires the "rand" feature to be enabled. 244 | /// let policy = RandomReplacement::with_rng(StdRng::seed_from_u64(42)); 245 | /// 246 | /// let cache = AssociativeCache::< 247 | /// PathBuf, 248 | /// File, 249 | /// Capacity128, 250 | /// HashEightWay, 251 | /// _, 252 | /// >::with_replacement_policy(policy); 253 | /// # } 254 | /// ``` 255 | pub fn with_replacement_policy(replacement_policy: R) -> Self { 256 | assert!(C::CAPACITY > 0); 257 | let mut entries = Vec::with_capacity(C::CAPACITY); 258 | for _ in 0..C::CAPACITY { 259 | entries.push(None); 260 | } 261 | AssociativeCache { 262 | entries, 263 | len: 0, 264 | replacement_policy, 265 | _capacity: PhantomData, 266 | _indices: PhantomData, 267 | } 268 | } 269 | 270 | /// Get a shared reference to this cache's replacement policy. 271 | #[inline] 272 | pub fn replacement_policy(&self) -> &R { 273 | &self.replacement_policy 274 | } 275 | 276 | /// Get an exclusive reference to this cache's replacement policy. 277 | #[inline] 278 | pub fn replacement_policy_mut(&mut self) -> &mut R { 279 | &mut self.replacement_policy 280 | } 281 | 282 | /// Get this cache's constant capacity, aka `C::CAPACITY`. 283 | #[inline] 284 | pub fn capacity(&self) -> usize { 285 | assert_eq!(self.entries.len(), C::CAPACITY); 286 | C::CAPACITY 287 | } 288 | 289 | /// Get the number of entries in this cache. 290 | /// 291 | /// This is always less than or equal to the capacity. 292 | /// 293 | /// ## Example 294 | /// 295 | /// ``` 296 | /// use associative_cache::*; 297 | /// 298 | /// let mut cache = AssociativeCache::< 299 | /// String, 300 | /// usize, 301 | /// Capacity16, 302 | /// HashDirectMapped, 303 | /// RoundRobinReplacement, 304 | /// >::default(); 305 | /// 306 | /// // Initially, the cache is empty. 307 | /// assert_eq!(cache.len(), 0); 308 | /// 309 | /// let old_entry = cache.insert("hi".to_string(), 2); 310 | /// 311 | /// // We know the cache was empty, so there can't be an old entry that was 312 | /// // replaced. 313 | /// assert!(old_entry.is_none()); 314 | /// 315 | /// // And now the length is 1. 316 | /// assert_eq!(cache.len(), 1); 317 | /// 318 | /// // Insert another entry. If this doesn't conflict with the existing 319 | /// // entry, then we should have a length of 2. If it did conflict, and we 320 | /// // replaced the old entry, then we should still have a length of 1. 321 | /// if cache.insert("bye".to_string(), 3).is_none() { 322 | /// assert_eq!(cache.len(), 2); 323 | /// } else { 324 | /// assert_eq!(cache.len(), 1); 325 | /// } 326 | /// ``` 327 | #[inline] 328 | pub fn len(&self) -> usize { 329 | debug_assert!(self.len <= self.capacity()); 330 | self.len 331 | } 332 | 333 | /// Return `true` if there are zero entries in the cache. 334 | #[inline] 335 | pub fn is_empty(&self) -> bool { 336 | self.len == 0 337 | } 338 | 339 | /// Insert a new entry into the cache. 340 | /// 341 | /// If there is an old entry for this key, or if another entry ends up 342 | /// getting replaced by this new one, return the old entry. 343 | /// 344 | /// ## Example 345 | /// 346 | /// ``` 347 | /// use associative_cache::*; 348 | /// 349 | /// let mut cache = AssociativeCache::< 350 | /// String, 351 | /// usize, 352 | /// Capacity1, 353 | /// HashDirectMapped, 354 | /// RoundRobinReplacement, 355 | /// >::default(); 356 | /// 357 | /// // Insert an entry for "hi" into the cache. 358 | /// let old_entry = cache.insert("hi".to_string(), 42); 359 | /// 360 | /// // The cache was empty, so no old entry. 361 | /// assert!(old_entry.is_none()); 362 | /// 363 | /// // Insert an entry for "bye" into the cache. 364 | /// let old_entry = cache.insert("bye".to_string(), 1337); 365 | /// 366 | /// // Because the cache only has a capacity of one, we replaced "hi" when 367 | /// // inserting "bye". 368 | /// assert_eq!(old_entry, Some(("hi".to_string(), 42))); 369 | /// ``` 370 | pub fn insert(&mut self, key: K, value: V) -> Option<(K, V)> 371 | where 372 | I: Indices, 373 | K: PartialEq, 374 | { 375 | let capacity = self.capacity(); 376 | 377 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 378 | enum InsertionCandidate { 379 | New(usize), 380 | Replace(usize), 381 | } 382 | assert!(None < Some(InsertionCandidate::New(0))); 383 | assert!(InsertionCandidate::New(0) < InsertionCandidate::Replace(0)); 384 | 385 | // First see if we can insert the value to an existing entry for this 386 | // key, or without replaceing any other entry. 387 | let mut best = None; 388 | for index in I::indices(&key) { 389 | assert!( 390 | index < capacity, 391 | "`Indices::indices` must always yield indices within the capacity" 392 | ); 393 | match self.entries[index] { 394 | None => { 395 | best = max(best, Some(InsertionCandidate::New(index))); 396 | } 397 | Some((ref k, _)) if *k == key => { 398 | best = max(best, Some(InsertionCandidate::Replace(index))); 399 | } 400 | _ => continue, 401 | } 402 | } 403 | 404 | match best { 405 | None => {} 406 | Some(InsertionCandidate::New(index)) => { 407 | self.entries[index] = Some((key, value)); 408 | self.len += 1; 409 | return None; 410 | } 411 | Some(InsertionCandidate::Replace(index)) => { 412 | return mem::replace(&mut self.entries[index], Some((key, value))); 413 | } 414 | } 415 | 416 | // Okay, we have to replace an entry. Let the `ReplacementPolicy` decide 417 | // which one. 418 | let AssociativeCache { 419 | ref entries, 420 | ref mut replacement_policy, 421 | .. 422 | } = self; 423 | let candidates = I::indices(&key).map(|index| { 424 | assert!( 425 | index < capacity, 426 | "`I::indices` must always yield indices within the capacity" 427 | ); 428 | let value = &entries[index] 429 | .as_ref() 430 | // We know that all the indices we saw above are full, so the 431 | // only way this `expect` would fail is if `Indices::indices` is 432 | // non-deterministic. 433 | .expect( 434 | "`Indices::indices` must always yield the same indices for the same entries", 435 | ) 436 | .1; 437 | (index, value) 438 | }); 439 | let index = replacement_policy.choose_for_replacement(candidates); 440 | debug_assert!( 441 | I::indices(&key).any(|i| i == index), 442 | "`ReplacementPolicy::choose_for_replacement` must return a candidate index" 443 | ); 444 | assert!(index < capacity); 445 | assert!(self.entries[index].is_some()); 446 | mem::replace(&mut self.entries[index], Some((key, value))) 447 | } 448 | 449 | /// Get a shared reference to the value for a given key, if it exists in the 450 | /// cache. 451 | /// 452 | /// ## Example 453 | /// 454 | /// ``` 455 | /// use associative_cache::*; 456 | /// 457 | /// let mut cache = AssociativeCache::< 458 | /// String, 459 | /// usize, 460 | /// Capacity1, 461 | /// HashDirectMapped, 462 | /// RoundRobinReplacement, 463 | /// >::default(); 464 | /// 465 | /// // Returns `None` if there is no cache entry for the key. 466 | /// assert!(cache.get("hi").is_none()); 467 | /// 468 | /// cache.insert("hi".to_string(), 1234); 469 | /// 470 | /// // Otherwise, returns the value if there is an entry for the key. 471 | /// assert_eq!(cache.get("hi"), Some(&1234)); 472 | /// ``` 473 | #[inline] 474 | pub fn get(&self, key: &Q) -> Option<&V> 475 | where 476 | K: Borrow, 477 | I: Indices, 478 | Q: ?Sized + PartialEq, 479 | { 480 | assert_eq!(self.entries.len(), C::CAPACITY); 481 | 482 | for index in I::indices(key) { 483 | assert!( 484 | index < self.entries.len(), 485 | "`Indices::indices` must always yield indices within the capacity" 486 | ); 487 | match &self.entries[index] { 488 | Some((k, v)) if k.borrow() == key => { 489 | self.replacement_policy.on_hit(v); 490 | return Some(v); 491 | } 492 | _ => continue, 493 | } 494 | } 495 | 496 | None 497 | } 498 | 499 | /// Get an exclusive reference to the value for a given key, if it exists in 500 | /// the cache. 501 | /// 502 | /// ## Example 503 | /// 504 | /// ``` 505 | /// use associative_cache::*; 506 | /// 507 | /// let mut cache = AssociativeCache::< 508 | /// String, 509 | /// usize, 510 | /// Capacity1, 511 | /// HashDirectMapped, 512 | /// RoundRobinReplacement, 513 | /// >::default(); 514 | /// 515 | /// // Returns `None` if there is no cache entry for the key. 516 | /// assert!(cache.get_mut("hi").is_none()); 517 | /// 518 | /// cache.insert("hi".to_string(), 1234); 519 | /// 520 | /// // Otherwise, returns the value if there is an entry for the key. 521 | /// let val = cache.get_mut("hi").unwrap(); 522 | /// assert_eq!(*val, 1234); 523 | /// 524 | /// // And we can assign to the cache value. 525 | /// *val = 5678; 526 | /// ``` 527 | #[inline] 528 | pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> 529 | where 530 | K: Borrow, 531 | I: Indices, 532 | Q: ?Sized + PartialEq, 533 | { 534 | assert_eq!(self.entries.len(), C::CAPACITY); 535 | 536 | for index in I::indices(key) { 537 | assert!( 538 | index < C::CAPACITY, 539 | "`Indices::indices` must always yield indices within the capacity" 540 | ); 541 | match &self.entries[index] { 542 | Some((k, _)) if k.borrow() == key => { 543 | let v = &mut self.entries[index].as_mut().unwrap().1; 544 | self.replacement_policy.on_hit(v); 545 | return Some(v); 546 | } 547 | _ => continue, 548 | } 549 | } 550 | 551 | None 552 | } 553 | 554 | /// Remove an entry from the cache. 555 | /// 556 | /// If an entry for the key existed in the cache, it is removed and `Some` 557 | /// is returned. Otherwise, `None` is returned. 558 | /// 559 | /// ## Example 560 | /// 561 | /// ``` 562 | /// use associative_cache::*; 563 | /// 564 | /// let mut cache = AssociativeCache::< 565 | /// String, 566 | /// usize, 567 | /// Capacity1, 568 | /// HashDirectMapped, 569 | /// RoundRobinReplacement, 570 | /// >::default(); 571 | /// 572 | /// // Returns `None` if there is no cache entry for the key and therefore 573 | /// // nothing was removed. 574 | /// assert!(cache.remove("hi").is_none()); 575 | /// 576 | /// cache.insert("hi".to_string(), 1234); 577 | /// 578 | /// // Otherwise, returns the value that was removed if there was an entry 579 | /// // for the key. 580 | /// assert_eq!(cache.remove("hi"), Some(1234)); 581 | /// ``` 582 | #[inline] 583 | pub fn remove(&mut self, key: &Q) -> Option 584 | where 585 | K: Borrow, 586 | I: Indices, 587 | Q: ?Sized + PartialEq, 588 | { 589 | assert_eq!(self.entries.len(), C::CAPACITY); 590 | 591 | for index in I::indices(key) { 592 | assert!( 593 | index < self.entries.len(), 594 | "`Indices::indices` must always yield indices within the capacity" 595 | ); 596 | match &self.entries[index] { 597 | Some((k, _)) if k.borrow() == key => { 598 | self.len -= 1; 599 | return self.entries[index].take().map(|(_, v)| v); 600 | } 601 | _ => continue, 602 | } 603 | } 604 | 605 | None 606 | } 607 | 608 | /// Retain only the cache entries specified by the predicate. 609 | /// 610 | /// Calls `f` with each entry in the cache, and removes all entries where 611 | /// `f` returned false. 612 | /// 613 | /// ## Example 614 | /// 615 | /// ``` 616 | /// use associative_cache::*; 617 | /// 618 | /// let mut cache = AssociativeCache::< 619 | /// char, 620 | /// usize, 621 | /// Capacity8, 622 | /// HashDirectMapped, 623 | /// RoundRobinReplacement, 624 | /// >::default(); 625 | /// 626 | /// for (i, ch) in "I let my tape rock, 'til my tape popped".char_indices() { 627 | /// cache.insert(ch, i); 628 | /// } 629 | /// 630 | /// for (key, val) in cache.iter() { 631 | /// println!("Last saw character '{}' at index {}", key, val); 632 | /// } 633 | /// ``` 634 | pub fn retain(&mut self, mut f: impl FnMut(&K, &mut V) -> bool) { 635 | for e in &mut self.entries { 636 | if let Some((k, v)) = e { 637 | if !f(k, v) { 638 | *e = None; 639 | self.len -= 1; 640 | } 641 | } 642 | } 643 | } 644 | 645 | /// Get the key's corresponding slot within the cache for in-place mutation 646 | /// and performing get-or-create operations. 647 | /// 648 | /// ## Example 649 | /// 650 | /// ``` 651 | /// use associative_cache::*; 652 | /// 653 | /// let mut cache = AssociativeCache::< 654 | /// String, 655 | /// usize, 656 | /// Capacity4, 657 | /// HashTwoWay, 658 | /// RoundRobinReplacement, 659 | /// >::default(); 660 | /// 661 | /// for word in "she sells sea shells down by the sea shore".split_whitespace() { 662 | /// let count = cache.entry(word).or_insert_with( 663 | /// || word.to_string(), 664 | /// || 0, 665 | /// ); 666 | /// *count += 1; 667 | /// } 668 | /// ``` 669 | #[inline] 670 | pub fn entry(&mut self, key: &Q) -> Entry 671 | where 672 | K: Borrow, 673 | I: Indices, 674 | Q: ?Sized + PartialEq, 675 | { 676 | let capacity = self.capacity(); 677 | 678 | // First, see if we have an entry for this key, or if we have an empty 679 | // slot where an entry could be placed without replaceing another entry. 680 | let mut empty_index = None; 681 | for index in I::indices(key) { 682 | assert!( 683 | index < capacity, 684 | "`Indices::indices` must always yield indices within the capacity" 685 | ); 686 | match &mut self.entries[index] { 687 | None => { 688 | empty_index = Some(index); 689 | } 690 | Some((k, v)) if (*k).borrow() == key => { 691 | self.replacement_policy.on_hit(v); 692 | return Entry { 693 | cache: self, 694 | kind: EntryKind::Occupied, 695 | index, 696 | }; 697 | } 698 | _ => continue, 699 | } 700 | } 701 | if let Some(index) = empty_index { 702 | return Entry { 703 | cache: self, 704 | kind: EntryKind::Vacant, 705 | index, 706 | }; 707 | } 708 | 709 | // Okay, we have to return an already-in-use entry, which will be 710 | // replaced if the user inserts anything. 711 | let AssociativeCache { 712 | ref entries, 713 | ref mut replacement_policy, 714 | .. 715 | } = self; 716 | let candidates = I::indices(key).map(|index| { 717 | assert!( 718 | index < capacity, 719 | "`I::indices` must always yield indices within the capacity" 720 | ); 721 | let value = &entries[index] 722 | .as_ref() 723 | // We know that all the indices we saw above are full, so the 724 | // only way this `expect` would fail is if `Indices::indices` is 725 | // non-deterministic. 726 | .expect( 727 | "`Indices::indices` must always yield the same indices for the same entries", 728 | ) 729 | .1; 730 | (index, value) 731 | }); 732 | let index = replacement_policy.choose_for_replacement(candidates); 733 | Entry { 734 | cache: self, 735 | kind: EntryKind::Replace, 736 | index, 737 | } 738 | } 739 | 740 | /// Iterate over shared references to this cache's keys and values. 741 | /// 742 | /// ## Example 743 | /// 744 | /// ``` 745 | /// use associative_cache::*; 746 | /// 747 | /// let mut cache = AssociativeCache::< 748 | /// String, 749 | /// usize, 750 | /// Capacity4, 751 | /// HashTwoWay, 752 | /// RoundRobinReplacement, 753 | /// >::default(); 754 | /// 755 | /// // First, insert some entries into the cache. Note that this is more 756 | /// // entries than the cache has capacity for. 757 | /// for s in vec!["red", "blue", "green", "pink", "purple", "orange"] { 758 | /// cache.insert(s.to_string(), s.len()); 759 | /// } 760 | /// 761 | /// // Now iterate over the entries that are still in the cache: 762 | /// for (k, v) in cache.iter() { 763 | /// println!("{} -> {}", k, v); 764 | /// } 765 | /// ``` 766 | #[inline] 767 | pub fn iter(&self) -> Iter { 768 | <&Self as IntoIterator>::into_iter(self) 769 | } 770 | 771 | /// Iterate over shared references to this cache's keys and exclusive 772 | /// references to its values. 773 | /// 774 | /// ## Example 775 | /// 776 | /// ``` 777 | /// use associative_cache::*; 778 | /// 779 | /// let mut cache = AssociativeCache::< 780 | /// String, 781 | /// usize, 782 | /// Capacity4, 783 | /// HashTwoWay, 784 | /// RoundRobinReplacement, 785 | /// >::default(); 786 | /// 787 | /// // First, insert some entries into the cache. Note that this is more 788 | /// // entries than the cache has capacity for. 789 | /// for s in vec!["red", "blue", "green", "pink", "purple", "orange"] { 790 | /// cache.insert(s.to_string(), s.len()); 791 | /// } 792 | /// 793 | /// // Now iterate over the entries that are still in the cache and mutate 794 | /// // them: 795 | /// for (k, v) in cache.iter_mut() { 796 | /// println!("{} was {}...", k, v); 797 | /// *v += 1; 798 | /// println!("...but now it's {}!", v); 799 | /// } 800 | /// ``` 801 | #[inline] 802 | pub fn iter_mut(&mut self) -> IterMut { 803 | <&mut Self as IntoIterator>::into_iter(self) 804 | } 805 | 806 | /// Consume this cache, and iterate over its keys and values. 807 | /// 808 | /// ## Example 809 | /// 810 | /// ``` 811 | /// use associative_cache::*; 812 | /// 813 | /// let mut cache = AssociativeCache::< 814 | /// String, 815 | /// usize, 816 | /// Capacity4, 817 | /// HashTwoWay, 818 | /// RoundRobinReplacement, 819 | /// >::default(); 820 | /// 821 | /// // First, insert some entries into the cache. Note that this is more 822 | /// // entries than the cache has capacity for. 823 | /// for s in vec!["red", "blue", "green", "pink", "purple", "orange"] { 824 | /// cache.insert(s.to_string(), s.len()); 825 | /// } 826 | /// 827 | /// // Not possible with `iter` or `iter_mut` without cloning. 828 | /// let v: Vec<(String, usize)> = cache.into_iter().collect(); 829 | /// ``` 830 | #[inline] 831 | #[allow(clippy::should_implement_trait)] 832 | pub fn into_iter(self) -> IntoIter { 833 | ::into_iter(self) 834 | } 835 | } 836 | 837 | #[cfg(test)] 838 | mod tests { 839 | use super::*; 840 | 841 | #[test] 842 | fn replacement_policy() { 843 | let mut policy = RoundRobinReplacement::default(); 844 | let mut cache = AssociativeCache::::with_replacement_policy(policy.clone()); 845 | assert_eq!(cache.replacement_policy(), &policy); 846 | assert_eq!(cache.replacement_policy_mut(), &mut policy); 847 | } 848 | 849 | #[test] 850 | fn capacity() { 851 | let cache = AssociativeCache::< 852 | usize, 853 | usize, 854 | Capacity2, 855 | HashDirectMapped, 856 | RoundRobinReplacement, 857 | >::default(); 858 | assert_eq!(cache.capacity(), 2); 859 | 860 | let cache = AssociativeCache::< 861 | usize, 862 | usize, 863 | Capacity4, 864 | HashDirectMapped, 865 | RoundRobinReplacement, 866 | >::default(); 867 | assert_eq!(cache.capacity(), 4); 868 | 869 | let cache = AssociativeCache::< 870 | usize, 871 | usize, 872 | Capacity8, 873 | HashDirectMapped, 874 | RoundRobinReplacement, 875 | >::default(); 876 | assert_eq!(cache.capacity(), 8); 877 | } 878 | 879 | #[test] 880 | fn len() { 881 | let mut cache = AssociativeCache::< 882 | usize, 883 | usize, 884 | Capacity512, 885 | HashDirectMapped, 886 | RoundRobinReplacement, 887 | >::default(); 888 | 889 | assert_eq!(cache.insert(1, 2), None); 890 | assert_eq!(cache.len(), 1); 891 | assert_eq!(cache.insert(3, 4), None); 892 | assert_eq!(cache.len(), 2); 893 | assert_eq!(cache.insert(5, 6), None); 894 | assert_eq!(cache.len(), 3); 895 | 896 | cache.insert(1, 7).unwrap(); 897 | assert_eq!(cache.len(), 3); 898 | cache.insert(3, 8).unwrap(); 899 | assert_eq!(cache.len(), 3); 900 | cache.insert(5, 9).unwrap(); 901 | assert_eq!(cache.len(), 3); 902 | } 903 | 904 | #[test] 905 | fn insert() { 906 | let mut cache = AssociativeCache::< 907 | *mut u8, 908 | usize, 909 | Capacity4, 910 | PointerTwoWay, 911 | RoundRobinReplacement, 912 | >::default(); 913 | 914 | // Fill all the cache slots. 915 | assert_eq!(cache.insert(0 as *mut u8, 0), None); 916 | assert_eq!(cache.insert(1 as *mut u8, 1), None); 917 | assert_eq!(cache.insert(2 as *mut u8, 2), None); 918 | assert_eq!(cache.insert(3 as *mut u8, 3), None); 919 | 920 | // Start replacing old entries with new insertions. 921 | assert_eq!(cache.insert(4 as *mut u8, 4), Some((2 as *mut u8, 2))); 922 | assert_eq!(cache.insert(6 as *mut u8, 6), Some((0 as *mut u8, 0))); 923 | assert_eq!(cache.insert(5 as *mut u8, 5), Some((3 as *mut u8, 3))); 924 | assert_eq!(cache.insert(7 as *mut u8, 7), Some((1 as *mut u8, 1))); 925 | } 926 | 927 | #[test] 928 | fn get() { 929 | let mut cache = AssociativeCache::< 930 | *mut u8, 931 | usize, 932 | Capacity4, 933 | PointerTwoWay, 934 | RoundRobinReplacement, 935 | >::default(); 936 | 937 | cache.insert(0 as *mut _, 0); 938 | assert_eq!(cache.get(&(0 as *mut _)), Some(&0)); 939 | assert_eq!(cache.get(&(1 as *mut _)), None); 940 | 941 | cache.insert(4 as *mut _, 4); 942 | assert_eq!(cache.get(&(0 as *mut _)), Some(&0)); 943 | assert_eq!(cache.get(&(4 as *mut _)), Some(&4)); 944 | assert_eq!(cache.get(&(1 as *mut _)), None); 945 | 946 | assert_eq!(cache.insert(8 as *mut _, 8), Some((4 as *mut _, 4))); 947 | assert_eq!(cache.get(&(0 as *mut _)), Some(&0)); 948 | assert_eq!(cache.get(&(8 as *mut _)), Some(&8)); 949 | assert_eq!(cache.get(&(1 as *mut _)), None); 950 | } 951 | 952 | #[test] 953 | fn get_mut() { 954 | let mut cache = AssociativeCache::< 955 | *mut u8, 956 | usize, 957 | Capacity4, 958 | PointerTwoWay, 959 | RoundRobinReplacement, 960 | >::default(); 961 | 962 | cache.insert(0 as *mut _, 0); 963 | assert_eq!(cache.get_mut(&(0 as *mut _)), Some(&mut 0)); 964 | assert_eq!(cache.get_mut(&(1 as *mut _)), None); 965 | 966 | cache.insert(4 as *mut _, 4); 967 | assert_eq!(cache.get_mut(&(0 as *mut _)), Some(&mut 0)); 968 | assert_eq!(cache.get_mut(&(4 as *mut _)), Some(&mut 4)); 969 | assert_eq!(cache.get_mut(&(1 as *mut _)), None); 970 | 971 | assert_eq!(cache.insert(8 as *mut _, 8), Some((4 as *mut _, 4))); 972 | assert_eq!(cache.get_mut(&(0 as *mut _)), Some(&mut 0)); 973 | assert_eq!(cache.get_mut(&(8 as *mut _)), Some(&mut 8)); 974 | assert_eq!(cache.get_mut(&(1 as *mut _)), None); 975 | } 976 | 977 | #[test] 978 | fn remove() { 979 | let mut cache = AssociativeCache::< 980 | *mut u8, 981 | usize, 982 | Capacity4, 983 | PointerTwoWay, 984 | RoundRobinReplacement, 985 | >::default(); 986 | 987 | cache.insert(0 as *mut _, 0); 988 | cache.insert(4 as *mut _, 4); 989 | assert_eq!(cache.len(), 2); 990 | 991 | assert_eq!(cache.remove(&(4 as *mut _)), Some(4)); 992 | assert_eq!(cache.remove(&(4 as *mut _)), None); 993 | assert_eq!(cache.remove(&(0 as *mut _)), Some(0)); 994 | assert_eq!(cache.remove(&(0 as *mut _)), None); 995 | } 996 | 997 | #[test] 998 | fn retain() { 999 | let mut cache = AssociativeCache::< 1000 | *mut u8, 1001 | usize, 1002 | Capacity4, 1003 | PointerTwoWay, 1004 | RoundRobinReplacement, 1005 | >::default(); 1006 | 1007 | cache.insert(0 as *mut _, 0); 1008 | cache.insert(1 as *mut _, 1); 1009 | cache.insert(2 as *mut _, 2); 1010 | cache.insert(3 as *mut _, 3); 1011 | assert_eq!(cache.len(), 4); 1012 | 1013 | cache.retain(|_, v| *v % 2 == 0); 1014 | assert_eq!(cache.len(), 2); 1015 | assert_eq!(cache.get(&(0 as *mut _)), Some(&0)); 1016 | assert_eq!(cache.get(&(1 as *mut _)), None); 1017 | assert_eq!(cache.get(&(2 as *mut _)), Some(&2)); 1018 | assert_eq!(cache.get(&(3 as *mut _)), None); 1019 | } 1020 | 1021 | #[test] 1022 | fn entry() { 1023 | let mut cache = AssociativeCache::< 1024 | *mut u8, 1025 | usize, 1026 | Capacity1, 1027 | PointerDirectMapped, 1028 | RoundRobinReplacement, 1029 | >::default(); 1030 | 1031 | // Vacant 1032 | assert_eq!( 1033 | cache 1034 | .entry(&(0 as *mut _)) 1035 | .or_insert_with(|| 0 as *mut _, || 0), 1036 | &mut 0 1037 | ); 1038 | assert_eq!(cache.len(), 1); 1039 | 1040 | // Occupied 1041 | assert_eq!( 1042 | cache 1043 | .entry(&(0 as *mut _)) 1044 | .or_insert_with(|| unreachable!(), || unreachable!()), 1045 | &mut 0 1046 | ); 1047 | assert_eq!(cache.len(), 1); 1048 | 1049 | // Replace 1050 | let mut entry = cache.entry(&(1 as *mut _)); 1051 | assert_eq!( 1052 | entry.take_entry_that_will_be_replaced(), 1053 | Some((0 as *mut _, 0)) 1054 | ); 1055 | assert_eq!(entry.or_insert_with(|| 1 as *mut _, || 1), &mut 1); 1056 | assert_eq!(cache.len(), 1); 1057 | } 1058 | 1059 | #[test] 1060 | fn iter() { 1061 | let mut cache = AssociativeCache::< 1062 | *mut u8, 1063 | usize, 1064 | Capacity4, 1065 | PointerDirectMapped, 1066 | RoundRobinReplacement, 1067 | >::default(); 1068 | 1069 | cache.insert(0 as *mut _, 0); 1070 | cache.insert(1 as *mut _, 1); 1071 | cache.insert(2 as *mut _, 2); 1072 | cache.insert(3 as *mut _, 3); 1073 | assert_eq!(cache.len(), 4); 1074 | 1075 | let mut seen = vec![false; 4]; 1076 | for (&k, &v) in &cache { 1077 | assert!(!seen[v]); 1078 | seen[v] = true; 1079 | assert_eq!(k as usize, v); 1080 | } 1081 | assert!(seen.iter().all(|&b| b)); 1082 | } 1083 | 1084 | #[test] 1085 | fn iter_mut() { 1086 | let mut cache = AssociativeCache::< 1087 | *mut u8, 1088 | usize, 1089 | Capacity4, 1090 | PointerDirectMapped, 1091 | RoundRobinReplacement, 1092 | >::default(); 1093 | 1094 | cache.insert(0 as *mut _, 0); 1095 | cache.insert(1 as *mut _, 1); 1096 | cache.insert(2 as *mut _, 2); 1097 | cache.insert(3 as *mut _, 3); 1098 | assert_eq!(cache.len(), 4); 1099 | 1100 | let mut seen = vec![false; 4]; 1101 | for (&k, v) in &mut cache { 1102 | assert!(!seen[*v]); 1103 | seen[*v] = true; 1104 | assert_eq!(k as usize, *v); 1105 | *v += 1; 1106 | } 1107 | assert!(seen.iter().all(|&b| b)); 1108 | 1109 | assert_eq!(cache.get(&(0 as *mut _)), Some(&1)); 1110 | assert_eq!(cache.get(&(1 as *mut _)), Some(&2)); 1111 | assert_eq!(cache.get(&(2 as *mut _)), Some(&3)); 1112 | assert_eq!(cache.get(&(3 as *mut _)), Some(&4)); 1113 | } 1114 | 1115 | #[test] 1116 | fn into_iter() { 1117 | let mut cache = AssociativeCache::< 1118 | *mut u8, 1119 | usize, 1120 | Capacity4, 1121 | PointerDirectMapped, 1122 | RoundRobinReplacement, 1123 | >::default(); 1124 | 1125 | cache.insert(0 as *mut _, 0); 1126 | cache.insert(1 as *mut _, 1); 1127 | cache.insert(2 as *mut _, 2); 1128 | cache.insert(3 as *mut _, 3); 1129 | assert_eq!(cache.len(), 4); 1130 | 1131 | let mut seen = vec![false; 4]; 1132 | for (k, v) in cache { 1133 | assert!(!seen[v]); 1134 | seen[v] = true; 1135 | assert_eq!(k as usize, v); 1136 | } 1137 | assert!(seen.iter().all(|&b| b)); 1138 | } 1139 | } 1140 | -------------------------------------------------------------------------------- /src/replacement.rs: -------------------------------------------------------------------------------- 1 | //! Implementations of various replacement algorithms used when inserting into a 2 | //! full cache. 3 | 4 | pub use super::{Capacity, Replacement}; 5 | 6 | pub mod lru; 7 | pub use lru::*; 8 | 9 | /// Choose cache entries to replace in a round-robin order. 10 | /// 11 | /// When considering `n` items to potentially replace, first it will replace the 12 | /// `0`th item, and then next time it will replace the `1`st item, ..., then the 13 | /// `n-1`th item, then the `0`th item, etc... 14 | /// 15 | /// This replacement policy is simple and fast, but can suffer from harmonics. 16 | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | pub struct RoundRobinReplacement { 18 | n: usize, 19 | } 20 | 21 | impl Replacement for RoundRobinReplacement 22 | where 23 | C: Capacity, 24 | { 25 | #[inline] 26 | fn choose_for_replacement<'a>( 27 | &mut self, 28 | mut candidates: impl ExactSizeIterator, 29 | ) -> usize 30 | where 31 | V: 'a, 32 | { 33 | let len = candidates.len(); 34 | assert!(len > 0); 35 | self.n %= len; 36 | let index = candidates.nth(self.n).unwrap().0; 37 | self.n += 1; 38 | index 39 | } 40 | } 41 | 42 | /// Choose a random cache entry to replace. 43 | /// 44 | /// When considering `n` items to potentially replace, choose one at random. 45 | /// 46 | /// **Requires the `"rand"` feature to be enabled.** 47 | #[cfg(feature = "rand")] 48 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 49 | pub struct RandomReplacement { 50 | rng: R, 51 | } 52 | 53 | #[cfg(feature = "rand")] 54 | impl Default for RandomReplacement { 55 | #[inline] 56 | fn default() -> Self { 57 | use rand::{Rng, SeedableRng}; 58 | let rng = rand::rngs::StdRng::seed_from_u64(rand::rngs::OsRng.gen()); 59 | RandomReplacement { rng } 60 | } 61 | } 62 | 63 | #[cfg(feature = "rand")] 64 | impl RandomReplacement { 65 | /// Construct a `RandomReplacement` with the given random number generator. 66 | /// 67 | /// ## Example 68 | /// 69 | /// ``` 70 | /// use associative_cache::*; 71 | /// use rand::{rngs::StdRng, SeedableRng}; 72 | /// 73 | /// let rng = StdRng::seed_from_u64(42); 74 | /// let policy = RandomReplacement::with_rng(rng); 75 | /// ``` 76 | #[inline] 77 | pub fn with_rng(rng: R) -> Self { 78 | RandomReplacement { rng } 79 | } 80 | } 81 | 82 | #[cfg(feature = "rand")] 83 | impl Replacement for RandomReplacement 84 | where 85 | C: Capacity, 86 | R: rand::Rng, 87 | { 88 | #[inline] 89 | fn choose_for_replacement<'a>( 90 | &mut self, 91 | candidates: impl Iterator, 92 | ) -> usize 93 | where 94 | V: 'a, 95 | { 96 | use rand::seq::IteratorRandom; 97 | candidates.choose(&mut self.rng).unwrap().0 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/replacement/lru.rs: -------------------------------------------------------------------------------- 1 | //! Least recently used (LRU) replacement policy implementation and traits for 2 | //! working with LRU timestamps. 3 | 4 | use super::*; 5 | use std::cell::Cell; 6 | use std::ops::{Deref, DerefMut}; 7 | use std::time::Instant; 8 | 9 | /// A trait for anything that has a timestamp that we can use with an LRU cache 10 | /// replacement policy. 11 | /// 12 | /// Don't already have a timestamp in your cache value? Consider using the 13 | /// `WithLruTimestamp` wrapper type around your cache value. That is likely a 14 | /// little easier than implementing this trait yourself. 15 | pub trait LruTimestamp { 16 | /// The timestamp type that will be compared. 17 | /// 18 | /// The entry with smallest timestamp value (according to its `PartialOrd` 19 | /// implementation) is the one that will be replaced. 20 | type Timestamp<'a>: PartialOrd 21 | where 22 | Self: 'a; 23 | 24 | /// Get this cache value's timestamp. 25 | fn get_timestamp(&self) -> Self::Timestamp<'_>; 26 | 27 | /// Update this cache value's timestamp. 28 | /// 29 | /// Note that this takes `&self`, not `&mut self`, because this is called on 30 | /// all cache hits, where we don't necessarily have `&mut` access to the 31 | /// cache. It is up to implementors to use internal mutability to update the 32 | /// timestamp. 33 | fn update_timestamp(&self); 34 | } 35 | 36 | /// A wrapper around a `T` cache value that maintains a timestamp for use with 37 | /// LRU cache replacement policies. 38 | /// 39 | /// Provides `Deref[Mut]` and `As{Ref,Mut}` implementations, so it is easy to 40 | /// drop in with minimal source changes. 41 | /// 42 | /// You can recover ownership of the inner `T` value via 43 | /// `WithLruTimestamp::into_inner(x)` once a value has been removed from the 44 | /// cache. 45 | /// 46 | /// # Example 47 | /// 48 | /// ``` 49 | /// use associative_cache::*; 50 | /// 51 | /// let cache = AssociativeCache::< 52 | /// String, 53 | /// // Wrap your cache value in `WithLruTimestamp`... 54 | /// WithLruTimestamp, 55 | /// Capacity128, 56 | /// HashEightWay, 57 | /// // ... and take advantage of LRU cache replacement! 58 | /// LruReplacement, 59 | /// >::default(); 60 | /// ``` 61 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 62 | pub struct WithLruTimestamp { 63 | timestamp: Cell, 64 | inner: T, 65 | } 66 | 67 | impl Default for WithLruTimestamp 68 | where 69 | T: Default, 70 | { 71 | #[inline] 72 | fn default() -> Self { 73 | WithLruTimestamp { 74 | timestamp: Cell::new(Instant::now()), 75 | inner: Default::default(), 76 | } 77 | } 78 | } 79 | 80 | impl AsRef for WithLruTimestamp { 81 | #[inline] 82 | fn as_ref(&self) -> &T { 83 | &self.inner 84 | } 85 | } 86 | 87 | impl AsMut for WithLruTimestamp { 88 | #[inline] 89 | fn as_mut(&mut self) -> &mut T { 90 | &mut self.inner 91 | } 92 | } 93 | 94 | impl Deref for WithLruTimestamp { 95 | type Target = T; 96 | 97 | #[inline] 98 | fn deref(&self) -> &T { 99 | &self.inner 100 | } 101 | } 102 | 103 | impl DerefMut for WithLruTimestamp { 104 | #[inline] 105 | fn deref_mut(&mut self) -> &mut T { 106 | &mut self.inner 107 | } 108 | } 109 | 110 | impl From for WithLruTimestamp { 111 | #[inline] 112 | fn from(inner: T) -> WithLruTimestamp { 113 | WithLruTimestamp::new(inner) 114 | } 115 | } 116 | 117 | impl WithLruTimestamp { 118 | /// Construct a new `WithLruTimestamp` wrapper around an inner value. 119 | /// 120 | /// ## Example 121 | /// 122 | /// ``` 123 | /// use associative_cache::*; 124 | /// 125 | /// let inner = "hello!".to_string(); 126 | /// let outer = WithLruTimestamp::new(inner); 127 | /// ``` 128 | #[inline] 129 | pub fn new(inner: T) -> WithLruTimestamp { 130 | WithLruTimestamp { 131 | timestamp: Cell::new(Instant::now()), 132 | inner, 133 | } 134 | } 135 | 136 | /// Recover the inner `T` value by consuming a `WithLruTimestamp`. 137 | /// 138 | /// ## Example 139 | /// 140 | /// ``` 141 | /// use associative_cache::*; 142 | /// 143 | /// let outer = WithLruTimestamp::new("hello!".to_string()); 144 | /// let inner = WithLruTimestamp::into_inner(outer); 145 | /// assert_eq!(inner, "hello!"); 146 | /// ``` 147 | #[inline] 148 | pub fn into_inner(outer: WithLruTimestamp) -> T { 149 | outer.inner 150 | } 151 | } 152 | 153 | impl LruTimestamp for WithLruTimestamp { 154 | type Timestamp<'a> = &'a Cell where T: 'a; 155 | 156 | #[inline] 157 | fn get_timestamp(&self) -> Self::Timestamp<'_> { 158 | &self.timestamp 159 | } 160 | 161 | #[inline] 162 | fn update_timestamp(&self) { 163 | self.timestamp.set(Instant::now()); 164 | } 165 | } 166 | 167 | /// Least recently used (LRU) cache replacement. 168 | /// 169 | /// When considering which one of N cache values to replace, choose the one that 170 | /// was least recently used. 171 | /// 172 | /// Requires that the cache value type implement `LruTimestamp`. 173 | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 174 | pub struct LruReplacement { 175 | _private: (), 176 | } 177 | 178 | impl Replacement for LruReplacement 179 | where 180 | C: Capacity, 181 | V: LruTimestamp, 182 | { 183 | #[inline] 184 | fn choose_for_replacement<'a>( 185 | &mut self, 186 | candidates: impl ExactSizeIterator, 187 | ) -> usize 188 | where 189 | V: 'a, 190 | { 191 | let mut lru = None; 192 | for (index, value) in candidates { 193 | let timestamp = value.get_timestamp(); 194 | lru = match lru { 195 | Some((t, i)) if t < timestamp => Some((t, i)), 196 | _ => Some((timestamp, index)), 197 | }; 198 | } 199 | lru.unwrap().1 200 | } 201 | 202 | #[inline] 203 | fn on_hit(&self, value: &V) { 204 | value.update_timestamp(); 205 | } 206 | 207 | #[inline] 208 | fn on_insert(&self, value: &V) { 209 | value.update_timestamp(); 210 | } 211 | } 212 | 213 | #[cfg(test)] 214 | mod tests { 215 | use super::*; 216 | use crate::Capacity4; 217 | use std::time::Duration; 218 | 219 | #[test] 220 | fn lru_replacement() { 221 | let now = Instant::now(); 222 | let candidates = vec![ 223 | now, 224 | now - Duration::from_secs(1), 225 | now - Duration::from_secs(2), 226 | now - Duration::from_secs(3), 227 | ] 228 | .into_iter() 229 | .map(|t| WithLruTimestamp { 230 | timestamp: Cell::new(t), 231 | inner: (), 232 | }) 233 | .collect::>(); 234 | 235 | let replacement = &mut LruReplacement::default(); 236 | 237 | let index = >::choose_for_replacement( 238 | replacement, 239 | candidates.iter().enumerate(), 240 | ); 241 | 242 | assert_eq!(index, 3); 243 | } 244 | 245 | #[test] 246 | fn lru_timestamp_ref() { 247 | struct Wrap { 248 | timestamp: Instant, 249 | } 250 | impl LruTimestamp for Wrap { 251 | type Timestamp<'a> = &'a Instant; 252 | fn get_timestamp(&self) -> Self::Timestamp<'_> { 253 | &self.timestamp 254 | } 255 | fn update_timestamp(&self) {} 256 | } 257 | let now = Instant::now(); 258 | let candidates = vec![ 259 | now, 260 | now - Duration::from_secs(1), 261 | now - Duration::from_secs(2), 262 | now - Duration::from_secs(3), 263 | ] 264 | .into_iter() 265 | .map(|t| Wrap { timestamp: t }) 266 | .collect::>(); 267 | 268 | let replacement = &mut LruReplacement::default(); 269 | 270 | let index = >::choose_for_replacement( 271 | replacement, 272 | candidates.iter().enumerate(), 273 | ); 274 | 275 | assert_eq!(index, 3); 276 | } 277 | 278 | #[test] 279 | fn lru_timestamp_owned() { 280 | #[repr(packed)] 281 | struct Wrap { 282 | timestamp: Instant, 283 | } 284 | impl LruTimestamp for Wrap { 285 | type Timestamp<'a> = Instant; 286 | fn get_timestamp(&self) -> Self::Timestamp<'_> { 287 | self.timestamp 288 | } 289 | fn update_timestamp(&self) {} 290 | } 291 | let now = Instant::now(); 292 | let candidates = vec![ 293 | now, 294 | now - Duration::from_secs(1), 295 | now - Duration::from_secs(2), 296 | now - Duration::from_secs(3), 297 | ] 298 | .into_iter() 299 | .map(|t| Wrap { timestamp: t }) 300 | .collect::>(); 301 | 302 | let replacement = &mut LruReplacement::default(); 303 | 304 | let index = >::choose_for_replacement( 305 | replacement, 306 | candidates.iter().enumerate(), 307 | ); 308 | 309 | assert_eq!(index, 3); 310 | } 311 | } 312 | --------------------------------------------------------------------------------