├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── scaling.rs └── src ├── iter.rs ├── lfu ├── entry.rs ├── freq_list.rs ├── lfu_entry.rs ├── mod.rs ├── node.rs └── util.rs ├── lib.rs └── timed_lfu.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install latest nightly 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: nightly 23 | components: miri 24 | - name: clippy 25 | run: cargo clippy 26 | - name: Build 27 | run: cargo build --verbose 28 | - name: Run miri tests 29 | run: MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | tarpaulin-report.html -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lfu_cache" 3 | description = "A simple constant time LFU cache implementation" 4 | version = "1.3.0" 5 | authors = ["Edward Shen "] 6 | edition = "2021" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/edward-shen/lfu-cache" 9 | keywords = ["lfu", "cache", "least", "frequently", "used"] 10 | categories = ["data-structures", "caching"] 11 | include = ["src/*", "LICENSE-*", "README.md"] 12 | 13 | [profile.release] 14 | lto = true 15 | codegen-units = 1 16 | 17 | [dev-dependencies] 18 | criterion = "0.4" 19 | 20 | [[bench]] 21 | name = "scaling" 22 | path = "benches/scaling.rs" 23 | harness = false 24 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Edward Shen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lfu-cache 2 | 3 | An implementation of a constant time Least Frequently Used (LFU) cache roughly 4 | based on the [paper by Shah, Mitra, and Matani][paper]. 5 | 6 | ## Example 7 | 8 | ```rust 9 | use lfu_cache::LfuCache; 10 | let mut cache = LfuCache::with_capacity(2); 11 | 12 | // Fill up the cache. 13 | cache.insert("foo", 3); 14 | cache.insert("bar", 4); 15 | 16 | // Insert returns the evicted value, if a value was evicted, in case additional 17 | // bookkeeping is necessary for the value to be dropped. 18 | let maybe_evicted = cache.insert("baz", 5); 19 | 20 | // In the case of a tie, the most recently added value is evicted. 21 | assert!(cache.get(&"bar").is_none()); 22 | assert_eq!(maybe_evicted, Some(4)); 23 | 24 | cache.get(&"baz"); 25 | // Otherwise, the least frequently value is evicted. 26 | assert_eq!(cache.pop_lfu(), Some(3)); 27 | ``` 28 | 29 | ## Reasons to use this implementation 30 | 31 | - Zero dependencies. 32 | - Small dependency, only providing the necessary features. 33 | - Fully documented and fully tested (including `miri`). 34 | - Respects Rust API guidelines: interface is implements most container APIs 35 | where possible. 36 | - Performant: Hits around 10 million insertions per section (using `i32` as 37 | elements; see `benches.rs` for bench implementation). 38 | 39 | ## Reasons not to use this implementation 40 | 41 | - Internally, this codebase uses `unsafe` as it works with raw pointers. 42 | - This can be considered a microcrate, which may be undesired if dependency 43 | count is a concern. 44 | 45 | ## Alternatives 46 | 47 | - Consider the [`lfu`] crate for an implementation written in only safe Rust. 48 | - Consider [`matthusifer/lfu_rs`][matt_lfu] for another implementation of the 49 | same paper. 50 | 51 | ## Deviances from the paper 52 | 53 | This implementation very closely follows the paper, but has one modification to 54 | ensure correctness. Each node in the _node list_ contains a `Rc` containing the 55 | key it was stored under, and the lookup table instead is indexed on a `Rc` 56 | instead. This is to ensure that the correct key-value in the lookup table can 57 | be evicted when popping the least frequently used item. 58 | 59 | This modification was necessary as the hash is _surjective_, and so each item 60 | necessarily needs to contain some reference to the original key it was stored 61 | under to ensure that we evict the correct key during hash collisions. 62 | 63 | An alternative solution would be to use an monotonically increasing counter, but 64 | the additional bookkeeping over an `Rc` which functionally provides the same 65 | benefit is unnecessary. 66 | 67 | ## License 68 | 69 | Licensed under either of 70 | 71 | * Apache License, Version 2.0 72 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 73 | * MIT license 74 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 75 | 76 | at your option. 77 | 78 | ## Contribution 79 | 80 | Unless you explicitly state otherwise, any contribution intentionally submitted 81 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 82 | dual licensed as above, without any additional terms or conditions. 83 | 84 | [paper]: http://dhruvbird.com/lfu.pdf 85 | [`lfu`]: https://crates.io/crates/lfu 86 | [matt_lfu]: https://github.com/mattusifer/lfu_rs -------------------------------------------------------------------------------- /benches/scaling.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; 2 | use lfu_cache::LfuCache; 3 | 4 | fn criterion_benchmark(c: &mut Criterion) { 5 | let mut group = c.benchmark_group("insertion unbounded"); 6 | for size in (1000..=10000).step_by(1000) { 7 | group.throughput(Throughput::Elements(size)); 8 | group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| { 9 | let mut cache = LfuCache::unbounded(); 10 | b.iter(|| { 11 | for i in 0..size { 12 | cache.insert(i, i); 13 | } 14 | }); 15 | }); 16 | } 17 | group.finish(); 18 | 19 | let mut group = c.benchmark_group("insertion bounded"); 20 | for size in (1000..=10000).step_by(1000) { 21 | group.throughput(Throughput::Elements(size)); 22 | group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| { 23 | let mut cache = LfuCache::with_capacity((size / 4) as usize); 24 | b.iter(|| { 25 | for i in 0..size { 26 | cache.insert(i, i); 27 | } 28 | }); 29 | }); 30 | } 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(benches, criterion_benchmark); 35 | criterion_main!(benches); 36 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hash; 2 | use std::iter::FusedIterator; 3 | 4 | use crate::LfuCache; 5 | 6 | /// A consuming iterator over the key and values of an LFU cache, in order of 7 | /// least frequently used first. 8 | /// 9 | /// This is constructed by calling `into_iter` on any cache implementation. 10 | // This is re-exported at the crate root, so this lint can be safely ignored. 11 | #[allow(clippy::module_name_repetitions)] 12 | pub struct LfuCacheIter(pub(crate) LfuCache); 13 | 14 | impl Iterator for LfuCacheIter { 15 | type Item = (Key, Value); 16 | 17 | fn next(&mut self) -> Option { 18 | self.0.pop_lfu_key_value() 19 | } 20 | 21 | fn size_hint(&self) -> (usize, Option) { 22 | (self.0.len(), Some(self.0.len())) 23 | } 24 | } 25 | 26 | impl FusedIterator for LfuCacheIter {} 27 | 28 | impl ExactSizeIterator for LfuCacheIter { 29 | #[inline] 30 | fn len(&self) -> usize { 31 | self.0.len() 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use crate::LfuCache; 38 | 39 | #[test] 40 | fn order_in_lfu() { 41 | let mut cache = LfuCache::unbounded(); 42 | for i in 0..10 { 43 | cache.insert(i, i); 44 | cache.get(&i); 45 | } 46 | 47 | let mut cache = cache.into_iter(); 48 | 49 | for i in (0..10).rev() { 50 | assert_eq!(cache.next(), Some((i, i))); 51 | } 52 | 53 | assert!(cache.next().is_none()); 54 | } 55 | 56 | #[test] 57 | fn size_is_correct() { 58 | let mut cache = LfuCache::unbounded(); 59 | for i in 0..10 { 60 | cache.insert(i, i); 61 | } 62 | 63 | let cache = cache.into_iter(); 64 | assert_eq!(cache.size_hint(), (10, Some(10))); 65 | assert_eq!(cache.len(), 10); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/lfu/entry.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::{ 2 | OccupiedEntry as InnerOccupiedEntry, VacantEntry as InnerVacantEntry, 3 | }; 4 | use std::hash::Hash; 5 | use std::num::NonZeroUsize; 6 | use std::ptr::NonNull; 7 | use std::rc::Rc; 8 | 9 | use super::util::remove_entry_pointer; 10 | use super::{FrequencyList, LfuEntry}; 11 | 12 | /// A view into a single entry in the LFU cache, which may either be vacant or 13 | /// occupied. 14 | /// 15 | /// This `enum` is constructed from the `entry` function on any of the LFU 16 | /// caches. 17 | pub enum Entry<'a, Key: Hash + Eq, Value> { 18 | /// An occupied entry. 19 | Occupied(OccupiedEntry<'a, Key, Value>), 20 | /// A vacant entry. 21 | Vacant(VacantEntry<'a, Key, Value>), 22 | } 23 | 24 | /// A view into an occupied entry in a LFU cache. It is part of the [`Entry`] 25 | /// enum. 26 | // This structure is re-exported at the root, so it's okay to be repetitive. 27 | #[allow(clippy::module_name_repetitions)] 28 | pub struct OccupiedEntry<'a, Key: Hash + Eq, Value> { 29 | inner: InnerOccupiedEntry<'a, Rc, NonNull>>, 30 | len: &'a mut usize, 31 | } 32 | 33 | impl<'a, Key: Hash + Eq, Value> OccupiedEntry<'a, Key, Value> { 34 | pub(super) fn new( 35 | entry: InnerOccupiedEntry<'a, Rc, NonNull>>, 36 | len: &'a mut usize, 37 | ) -> Self { 38 | Self { inner: entry, len } 39 | } 40 | 41 | /// Gets a reference to the key in the entry. 42 | #[inline] 43 | #[must_use] 44 | pub fn key(&self) -> &Key { 45 | self.inner.key() 46 | } 47 | 48 | /// Take the ownership of the key and value from the map. 49 | #[must_use] 50 | pub fn remove_entry(self) -> (Rc, Value) { 51 | let (key, node) = self.inner.remove_entry(); 52 | let node = *unsafe { Box::from_raw(node.as_ptr()) }; 53 | let value = remove_entry_pointer(node, self.len); 54 | (key, value) 55 | } 56 | 57 | /// Gets a reference to the value in the entry. 58 | #[inline] 59 | #[must_use] 60 | pub fn get(&self) -> &Value { 61 | &unsafe { self.inner.get().as_ref() }.value 62 | } 63 | 64 | /// Gets a mutable reference to the value in the entry. 65 | /// 66 | /// If you need a reference to the `OccupiedEntry` which may outlive the 67 | /// destruction of the Entry value, see [`Self::into_mut`]. 68 | #[inline] 69 | pub fn get_mut(&mut self) -> &mut Value { 70 | &mut unsafe { self.inner.get_mut().as_mut() }.value 71 | } 72 | 73 | /// Converts the `OccupiedEntry` into a mutable reference to the value in 74 | /// the entry with a lifetime bound to the map itself. 75 | /// 76 | /// If you need multiple references to the `OccupiedEntry`, see 77 | /// [`Self::get_mut`]. 78 | #[inline] 79 | #[must_use] 80 | pub fn into_mut(self) -> &'a mut Value { 81 | &mut unsafe { self.inner.into_mut().as_mut() }.value 82 | } 83 | 84 | /// Sets the value of the entry, and returns the entry's old value. Note 85 | /// that the semantics for this method is closer to swapping the values 86 | /// rather than inserting a new value into the LFU cache. As a result, this 87 | /// does not reset the frequency of the key. 88 | pub fn insert(&mut self, mut value: Value) -> Value { 89 | let old_value = &mut unsafe { self.inner.get_mut().as_mut() }.value; 90 | std::mem::swap(old_value, &mut value); 91 | value 92 | } 93 | 94 | /// Takes the value out of the entry, and returns it. 95 | #[must_use] 96 | pub fn remove(self) -> Value { 97 | let node = self.inner.remove(); 98 | let node = *unsafe { Box::from_raw(node.as_ptr()) }; 99 | remove_entry_pointer(node, self.len) 100 | } 101 | } 102 | 103 | /// A view into a vacant entry in a LFU cache. It is part of the [`Entry`] enum. 104 | // This structure is re-exported at the root, so it's okay to be repetitive. 105 | #[allow(clippy::module_name_repetitions)] 106 | pub struct VacantEntry<'a, Key: Hash + Eq, Value> { 107 | inner: InnerVacantEntry<'a, Rc, NonNull>>, 108 | key: Rc, 109 | freq_list: &'a mut FrequencyList, 110 | cache_capacity: Option, 111 | cache_len: &'a mut usize, 112 | } 113 | 114 | impl<'a, Key: Hash + Eq, Value> VacantEntry<'a, Key, Value> { 115 | pub(super) fn new( 116 | entry: InnerVacantEntry<'a, Rc, NonNull>>, 117 | key: Rc, 118 | freq_list: &'a mut FrequencyList, 119 | cache_capacity: Option, 120 | cache_len: &'a mut usize, 121 | ) -> Self { 122 | Self { 123 | inner: entry, 124 | key, 125 | freq_list, 126 | cache_capacity, 127 | cache_len, 128 | } 129 | } 130 | 131 | /// Gets a reference to the key that would be used when inserting a value 132 | /// through the [`VacantEntry`]. 133 | #[inline] 134 | #[must_use] 135 | pub fn key(&self) -> &Key { 136 | self.key.as_ref() 137 | } 138 | 139 | /// Gets a [`Rc`] to the key that would be used when inserting a value 140 | /// through the [`VacantEntry`]. 141 | #[inline] 142 | #[must_use] 143 | pub fn key_rc(&self) -> Rc { 144 | Rc::clone(&self.key) 145 | } 146 | 147 | /// Take ownership of the key. 148 | #[inline] 149 | #[must_use] 150 | // False positive, const can't evaluate self dropping. 151 | #[allow(clippy::missing_const_for_fn)] 152 | pub fn into_key(self) -> Rc { 153 | self.key 154 | } 155 | 156 | /// Sets the value of the entry with the [`VacantEntry`]'s key, and returns 157 | /// a mutable reference to it. 158 | #[inline] 159 | pub fn insert(self, value: Value) -> &'a mut Value { 160 | if let Some(capacity) = self.cache_capacity { 161 | if capacity.get() == *self.cache_len { 162 | self.freq_list.pop_lfu(); 163 | } 164 | } else { 165 | *self.cache_len += 1; 166 | } 167 | 168 | &mut unsafe { 169 | self.inner 170 | .insert(self.freq_list.insert(self.key, value)) 171 | .as_mut() 172 | } 173 | .value 174 | } 175 | } 176 | 177 | impl<'a, Key: Hash + Eq, Value> Entry<'a, Key, Value> { 178 | /// Ensures a value is in the entry by inserting the default if empty, and 179 | /// returns a mutable reference to the value in the entry. 180 | #[inline] 181 | pub fn or_insert(self, default: Value) -> &'a mut Value { 182 | match self { 183 | Entry::Occupied(entry) => &mut unsafe { entry.inner.into_mut().as_mut() }.value, 184 | Entry::Vacant(entry) => entry.insert(default), 185 | } 186 | } 187 | 188 | /// Ensures a value is in the entry by inserting the result of the default 189 | /// function if empty, and returns a mutable reference to the value in the 190 | /// entry. 191 | #[inline] 192 | pub fn or_insert_with(self, default: F) -> &'a mut Value 193 | where 194 | F: FnOnce() -> Value, 195 | { 196 | match self { 197 | Entry::Occupied(entry) => &mut unsafe { entry.inner.into_mut().as_mut() }.value, 198 | Entry::Vacant(entry) => entry.insert(default()), 199 | } 200 | } 201 | 202 | /// Ensures a value is in the entry by inserting, if empty, the result of 203 | /// the default function. This method allows for generating key-derived 204 | /// values for insertion by providing the default function a reference to 205 | /// the key that was moved during the `.entry(key)` method call. 206 | /// 207 | /// The reference to the moved key is provided so that cloning or copying 208 | /// the key is unnecessary, unlike with `.or_insert_with(|| ... )`. 209 | #[inline] 210 | pub fn or_insert_with_key(self, default: F) -> &'a mut Value 211 | where 212 | F: FnOnce(&Key) -> Value, 213 | { 214 | match self { 215 | Entry::Occupied(entry) => &mut unsafe { entry.inner.into_mut().as_mut() }.value, 216 | Entry::Vacant(entry) => { 217 | let value = default(&entry.key); 218 | entry.insert(value) 219 | } 220 | } 221 | } 222 | 223 | /// Returns a reference to this entry's key. 224 | #[inline] 225 | #[must_use] 226 | pub fn key(&self) -> &Key { 227 | match self { 228 | Entry::Occupied(entry) => entry.inner.key(), 229 | Entry::Vacant(entry) => entry.key.as_ref(), 230 | } 231 | } 232 | 233 | /// Returns the `Rc` to this entry's key. 234 | #[inline] 235 | #[must_use] 236 | pub fn key_rc(&self) -> Rc { 237 | match self { 238 | Entry::Occupied(entry) => Rc::clone(entry.inner.key()), 239 | Entry::Vacant(entry) => Rc::clone(&entry.key), 240 | } 241 | } 242 | 243 | /// Provides in-place mutable access to an occupied entry before any 244 | /// potential inserts into the map. 245 | #[inline] 246 | #[must_use] 247 | pub fn and_modify(self, f: F) -> Self 248 | where 249 | F: FnOnce(&mut Value), 250 | { 251 | match self { 252 | Self::Occupied(mut entry) => { 253 | f(&mut unsafe { entry.inner.get_mut().as_mut() }.value); 254 | Self::Occupied(entry) 255 | } 256 | Self::Vacant(entry) => Self::Vacant(entry), 257 | } 258 | } 259 | } 260 | 261 | impl<'a, Key: Hash + Eq, Value: Default> Entry<'a, Key, Value> { 262 | /// Ensures a value is in the entry by inserting the default value if empty, 263 | /// and returns a mutable reference to the value in the entry. 264 | #[inline] 265 | #[must_use] 266 | pub fn or_default(self) -> &'a mut Value { 267 | match self { 268 | Self::Occupied(entry) => &mut unsafe { entry.inner.into_mut().as_mut() }.value, 269 | Self::Vacant(entry) => entry.insert(Value::default()), 270 | } 271 | } 272 | } 273 | 274 | #[cfg(test)] 275 | mod entry { 276 | use crate::LfuCache; 277 | 278 | fn init_cache() -> LfuCache { 279 | LfuCache::unbounded() 280 | } 281 | 282 | #[test] 283 | fn or_insert_empty_adds_value() { 284 | let mut cache = init_cache(); 285 | let entry = cache.entry(1); 286 | 287 | // test entry value is expected 288 | let v = entry.or_insert(2); 289 | assert_eq!(*v, 2); 290 | 291 | // test cache has been updated correctly 292 | assert_eq!(cache.keys().copied().collect::>(), vec![1]); 293 | assert_eq!(cache.frequencies(), vec![0]); 294 | assert_eq!(cache.get(&1), Some(&2)); 295 | assert_eq!(cache.len(), 1); 296 | } 297 | 298 | #[test] 299 | fn or_insert_non_empty_does_nothing() { 300 | let mut cache = init_cache(); 301 | cache.insert(1, 2); 302 | let entry = cache.entry(1); 303 | 304 | // test entry value is expected 305 | let v = entry.or_insert(3); 306 | assert_eq!(*v, 2); 307 | 308 | // test cache has been updated correctly 309 | assert_eq!(cache.keys().copied().collect::>(), vec![1]); 310 | assert_eq!(cache.frequencies(), vec![1]); 311 | assert_eq!(cache.get(&1), Some(&2)); 312 | assert_eq!(cache.len(), 1); 313 | } 314 | 315 | #[test] 316 | fn or_insert_with_is_equiv_to_or_insert() { 317 | // empty cache 318 | let mut cache_0 = init_cache(); 319 | let res_0 = cache_0.entry(1).or_insert(2); 320 | let mut cache_1 = init_cache(); 321 | let res_1 = cache_1.entry(1).or_insert_with(|| 2); 322 | assert_eq!(res_0, res_1); 323 | 324 | // non-empty cache 325 | let mut cache_0 = init_cache(); 326 | cache_0.insert(1, 3); 327 | let res_0 = cache_0.entry(1).or_insert(2); 328 | let mut cache_1 = init_cache(); 329 | cache_1.insert(1, 3); 330 | let res_1 = cache_1.entry(1).or_insert_with(|| 2); 331 | assert_eq!(res_0, res_1); 332 | } 333 | 334 | #[test] 335 | fn or_insert_with_key_is_equiv_to_or_insert() { 336 | // empty cache 337 | let mut cache_0 = init_cache(); 338 | let res_0 = cache_0.entry(1).or_insert(2); 339 | let mut cache_1 = init_cache(); 340 | let res_1 = cache_1.entry(1).or_insert_with_key(|_| 2); 341 | assert_eq!(res_0, res_1); 342 | 343 | // non-empty cache 344 | let mut cache_0 = init_cache(); 345 | cache_0.insert(1, 3); 346 | let res_0 = cache_0.entry(1).or_insert(2); 347 | let mut cache_1 = init_cache(); 348 | cache_1.insert(1, 3); 349 | let res_1 = cache_1.entry(1).or_insert_with_key(|_| 2); 350 | assert_eq!(res_0, res_1); 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/lfu/freq_list.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Display, Formatter}; 2 | use std::hash::Hash; 3 | use std::iter::FusedIterator; 4 | use std::ptr::NonNull; 5 | use std::rc::Rc; 6 | 7 | use super::lfu_entry::Detached; 8 | use super::node::WithFrequency; 9 | use super::{LfuEntry, Node}; 10 | 11 | /// Represents the internal data structure to determine frequencies of some 12 | /// items. 13 | /// 14 | /// An frequency list is a doubly-linked list consisting of [`Node`]s pointing 15 | /// to a doubly linked list of [`LfuEntry`]s. Each [`LfuEntry`] has an 16 | /// associated key-value pair, and knows the node it's rooted under. Each 17 | /// [`Node`] knows its frequency, as well as the first element into the 18 | /// doubly-linked list. 19 | /// 20 | /// The doubly-linked [`LfuEntry`]s allow for constant time removal. The 21 | /// [`Node`] having a reference to the [`LfuEntry`]s allow for constant time 22 | /// insertion and easy access when popping off an LFU entry. All [`LfuEntry`]s 23 | /// know its [`Node`] owner to quickly allow for removal and re-insertion into 24 | /// the next node. 25 | /// 26 | /// For example, the following is a representation of a frequency list with 27 | /// two items accessed once, one item access 3 times, and three items accessed 28 | /// four times: 29 | /// 30 | /// ```text 31 | /// ┌────┐ ┌────┐ ┌────┐ 32 | /// │Node◄───────────┤Node◄───────────┤Node│ 33 | /// │(1) │ │(3) │ │(4) │ 34 | /// │ ├─┬─────────► ├─┬─────────► ├─┐ 35 | /// └─▲──┘ │ └─▲──┘ │ └─▲──┘ │ 36 | /// │ │ │ │ │ │ 37 | /// │ │ │ │ │ │ 38 | /// │ ┌─▼──────┐ │ ┌─▼──────┐ │ ┌─▼──────┐ 39 | /// ├──┤LfuEntry│ └──┤LfuEntry│ ├──┤LfuEntry│ 40 | /// │ │(K, V) │ │(K, V) │ │ │(K, V) │ 41 | /// │ └─┬────▲─┘ └────────┘ │ └─┬────▲─┘ 42 | /// │ │ │ │ │ │ 43 | /// │ │ │ │ │ │ 44 | /// │ ┌─▼────┴─┐ │ ┌─▼────┴─┐ 45 | /// └──┤LfuEntry│ ├──┤LfuEntry│ 46 | /// │(K, V) │ │ │(K, V) │ 47 | /// └────────┘ │ └─┬────▲─┘ 48 | /// │ │ │ 49 | /// │ │ │ 50 | /// │ ┌─▼────┴─┐ 51 | /// └──┤LfuEntry│ 52 | /// │(K, V) │ 53 | /// └────────┘ 54 | /// ``` 55 | /// 56 | /// It currently is illegal for a [`Node`] to exist but have no child elements. 57 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash)] 58 | pub(super) struct FrequencyList { 59 | /// The first node in the frequency list which may or may not exist. This 60 | /// item is heap allocated. 61 | pub(super) head: Option>>, 62 | } 63 | 64 | impl Default for FrequencyList { 65 | fn default() -> Self { 66 | Self::new() 67 | } 68 | } 69 | 70 | #[cfg(not(tarpaulin_include))] 71 | impl Debug for FrequencyList { 72 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 73 | let mut dbg = f.debug_struct("FrequencyList"); 74 | let mut node = self.head; 75 | while let Some(cur_node) = node { 76 | let cur_node = unsafe { cur_node.as_ref() }; 77 | dbg.field( 78 | &format!("node freq {} num elements", &cur_node.frequency), 79 | &cur_node.len(), 80 | ); 81 | node = cur_node.next; 82 | } 83 | 84 | dbg.finish() 85 | } 86 | } 87 | 88 | #[cfg(not(tarpaulin_include))] 89 | impl Display for FrequencyList { 90 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 91 | let mut cur_node = self.head; 92 | 93 | while let Some(ref node) = cur_node { 94 | let node = unsafe { node.as_ref() }; 95 | writeln!(f, " Node (freq value = {}) [", node.frequency)?; 96 | let mut cur_ele = node.elements; 97 | while let Some(ref ele) = cur_ele { 98 | let ele = unsafe { ele.as_ref() }; 99 | writeln!(f, " {},", ele.value)?; 100 | cur_ele = ele.next; 101 | } 102 | writeln!(f, " ]")?; 103 | cur_node = node.next; 104 | } 105 | Ok(()) 106 | } 107 | } 108 | 109 | impl Drop for FrequencyList { 110 | fn drop(&mut self) { 111 | if let Some(ptr) = self.head { 112 | // SAFETY: self is exclusively accessed 113 | unsafe { Box::from_raw(ptr.as_ptr()) }; 114 | } 115 | } 116 | } 117 | 118 | impl FrequencyList { 119 | #[inline] 120 | pub(super) const fn new() -> Self { 121 | Self { head: None } 122 | } 123 | 124 | /// Inserts an item into the frequency list returning a pointer to the 125 | /// item. 126 | /// 127 | /// Since this is a newly inserted item, it will always have an access count 128 | /// of zero. 129 | /// 130 | /// # Memory ownership 131 | /// 132 | /// It is the caller's responsibility to free the returning pointer, usually 133 | /// via `Box::from_raw(foo.as_ptr())`. 134 | pub(super) fn insert(&mut self, key: Rc, value: T) -> NonNull> { 135 | // Gets or creates a node with a frequency of zero. 136 | // Lint false positive; the match guard is unaccounted for. 137 | #[allow(clippy::option_if_let_else)] 138 | let head = match self.head { 139 | Some(head) if unsafe { head.as_ref() }.frequency == 0 => head, 140 | _ => self.init_front(), 141 | }; 142 | 143 | Node::push(head, Detached::new(key, value)) 144 | } 145 | 146 | /// Creates a new node at the beginning of the frequency list with an access 147 | /// count of zero. 148 | /// 149 | /// # Memory ownership 150 | /// 151 | /// The returned pointer does not need to be freed. This method internally 152 | /// updates the head of the list to be the pointer, which will free the 153 | /// pointer on drop. 154 | fn init_front(&mut self) -> NonNull> { 155 | let node = Box::new(Node { 156 | next: self.head, 157 | prev: None, 158 | elements: None, 159 | frequency: 0, 160 | }); 161 | 162 | let node = NonNull::from(Box::leak(node)); 163 | 164 | if let Some(mut head) = self.head { 165 | // SAFETY: self is exclusively accessed 166 | if let Some(mut next) = unsafe { head.as_ref() }.next { 167 | // SAFETY: self is exclusively accessed 168 | let next = unsafe { next.as_mut() }; 169 | next.prev = Some(head); 170 | } 171 | 172 | let head = unsafe { head.as_mut() }; 173 | head.prev = Some(node); 174 | } 175 | 176 | self.head = Some(node); 177 | 178 | node 179 | } 180 | 181 | /// Updates the "frequency" of the entry. 182 | /// 183 | /// This in practice, gets or creates a [`Node`] with frequency + 1 of the 184 | /// entry. It then detaches itself from it's owning [`Node`], and reattaches 185 | /// itself to the frequency + 1 [`Node`]. 186 | /// 187 | /// If the old [`Node`] no longer has any entries, the [`Node`] is removed. 188 | // TODO: Brand LfuEntry? 189 | pub(super) fn update(&mut self, mut entry: NonNull>) { 190 | // Generate the next frequency list node if it doesn't exist or isn't 191 | // n + 1 of the current node's frequency. 192 | // SAFETY: self is exclusively accessed 193 | let freq_list_node = unsafe { (*entry.as_ptr()).owner.as_ptr() }; 194 | let freq_list_node_freq = unsafe { &*freq_list_node }.frequency; 195 | // Create next node if needed 196 | // false positive, lint doesn't respect match guard. 197 | #[allow(clippy::option_if_let_else)] 198 | let next_node = match unsafe { &*freq_list_node }.next { 199 | // SAFETY: self is exclusively accessed 200 | Some(node) if unsafe { node.as_ref() }.frequency == freq_list_node_freq + 1 => node, 201 | _ => Node::create_increment(NonNull::new(freq_list_node).unwrap()), 202 | }; 203 | 204 | // Remove from current frequency node 205 | let freq_list_node = unsafe { entry.as_mut().owner.as_mut() }; 206 | let detached = freq_list_node.remove_ref(entry); 207 | 208 | // Insert into next node 209 | Node::push_ref(next_node, detached); 210 | 211 | // Drop frequency list node if it contains no elements 212 | if freq_list_node.elements.is_none() { 213 | let freq_head = unsafe { Box::from_raw(freq_list_node) }; 214 | if self.head == Some(NonNull::from(&*freq_head)) { 215 | self.head = freq_head.next; 216 | } 217 | 218 | freq_head.detach(); 219 | } 220 | } 221 | 222 | /// Removes the first entry of the head element if the element exists. 223 | /// 224 | /// Since the first entry of the head element is the most recently added 225 | /// item, popping elements of the same frequency is Last In, First Out. In 226 | /// other words, the lowest frequency items are selected, and of those 227 | /// items, they are popped like a stack. 228 | #[inline] 229 | pub(super) fn pop_lfu(&mut self) -> Option>> { 230 | let mut head_node_ptr = self.head?; 231 | let head_node = unsafe { head_node_ptr.as_mut() }; 232 | 233 | let item = head_node.pop(); 234 | if head_node.elements.is_none() { 235 | // Remove the now empty head 236 | self.head = head_node.next; 237 | 238 | let owned = unsafe { Box::from_raw(head_node_ptr.as_ptr()) }; 239 | owned.detach(); 240 | } 241 | item 242 | } 243 | 244 | /// Returns the most recently added, lowest frequently accessed item if it 245 | /// exists. 246 | #[inline] 247 | pub(super) fn peek_lfu(&self) -> Option<&T> { 248 | self.head.and_then(|node| unsafe { node.as_ref() }.peek()) 249 | } 250 | 251 | /// Returns the key of the most recently added, lowest frequently accessed 252 | /// item if it exists. 253 | #[inline] 254 | pub(super) fn peek_lfu_key(&self) -> Option<&Key> { 255 | self.head 256 | .and_then(|node| unsafe { node.as_ref() }.peek_key()) 257 | } 258 | 259 | /// Returns an iterator of all frequencies in the list. 260 | pub(super) fn frequencies(&self) -> impl Iterator + FusedIterator + '_ { 261 | self.iter().map(|node| node.frequency) 262 | } 263 | 264 | /// Iterates through the frequency list, returning the number of [`Node`]s 265 | /// in the list. 266 | #[cfg(test)] 267 | pub fn len(&self) -> usize { 268 | self.iter().count() 269 | } 270 | 271 | /// Returns an iterator over all [`Node`]s in the frequency list. 272 | fn iter(&self) -> Iter<'_, Key, T> { 273 | Iter(self.head.map(|v| unsafe { v.as_ref() })) 274 | } 275 | } 276 | 277 | /// An iterator over the [`Node`]s in the frequency list. 278 | /// 279 | /// This is created by [`FrequencyList::iter`]. 280 | // Note that this internally contains a reference to a Node rather than a 281 | // pointer to one. This is intentional to associate the lifetime of Iter to the 282 | // derived frequency list. 283 | #[derive(Debug)] 284 | struct Iter<'a, Key: Hash + Eq, Value>(Option<&'a Node>); 285 | 286 | impl<'a, Key: Hash + Eq, Value> Iterator for Iter<'a, Key, Value> { 287 | type Item = &'a Node; 288 | 289 | fn next(&mut self) -> Option { 290 | let ret = self.0?; 291 | self.0 = ret.next.map(|v| unsafe { v.as_ref() }); 292 | Some(ret) 293 | } 294 | } 295 | 296 | impl<'a, Key: Hash + Eq, Value> FusedIterator for Iter<'a, Key, Value> {} 297 | 298 | #[cfg(test)] 299 | mod frequency_list { 300 | use std::{ptr::NonNull, rc::Rc}; 301 | 302 | use super::FrequencyList; 303 | 304 | fn init_list() -> FrequencyList { 305 | FrequencyList::new() 306 | } 307 | 308 | #[test] 309 | fn new_is_empty() { 310 | let list = init_list(); 311 | assert!(list.head.is_none()); 312 | assert_eq!(list.len(), 0); 313 | assert!(list.frequencies().count() == 0); 314 | } 315 | 316 | #[test] 317 | fn insert() { 318 | let mut list = init_list(); 319 | let entry = unsafe { Box::from_raw(list.insert(Rc::new(1), 2).as_ptr()) }; 320 | assert_eq!(entry.prev, None); 321 | assert_eq!(entry.next, None); 322 | assert_eq!(entry.value, 2); 323 | assert_eq!(entry.owner, list.head.unwrap()); 324 | } 325 | 326 | #[test] 327 | fn insert_non_empty() { 328 | let mut list = init_list(); 329 | let entry_0 = list.insert(Rc::new(1), 2); 330 | let entry_1 = list.insert(Rc::new(3), 4); 331 | 332 | let entry_0_ref = unsafe { entry_0.as_ref() }; 333 | let entry_1_ref = unsafe { entry_1.as_ref() }; 334 | 335 | // validate entry_1 336 | assert_eq!(entry_1_ref.prev, None); 337 | assert_eq!(entry_1_ref.next, Some(entry_0)); 338 | assert_eq!(entry_1_ref.value, 4); 339 | assert_eq!(entry_1_ref.owner, list.head.unwrap()); 340 | 341 | // validate entry_0 342 | assert_eq!(entry_0_ref.prev, Some(entry_1)); 343 | assert_eq!(entry_0_ref.next, None); 344 | assert_eq!(entry_0_ref.value, 2); 345 | assert_eq!(entry_0_ref.owner, list.head.unwrap()); 346 | 347 | unsafe { 348 | drop(Box::from_raw(entry_0.as_ptr())); 349 | drop(Box::from_raw(entry_1.as_ptr())); 350 | } 351 | } 352 | 353 | #[test] 354 | fn insert_non_empty_non_freq_zero() { 355 | let mut list = init_list(); 356 | let entry_0_ptr = list.insert(Rc::new(1), 2).as_ptr(); 357 | list.update(NonNull::new(entry_0_ptr).unwrap()); 358 | let entry_1_ptr = list.insert(Rc::new(3), 4).as_ptr(); 359 | 360 | // validate entry_0 361 | let entry_0 = unsafe { &*entry_0_ptr }; 362 | assert_eq!(entry_0.prev, None); 363 | assert_eq!(entry_0.next, None); 364 | assert_eq!(entry_0.value, 2); 365 | assert_ne!(entry_0.owner, list.head.unwrap()); 366 | 367 | // validate entry_1 368 | let entry_1 = unsafe { &*entry_1_ptr }; 369 | assert_eq!(entry_1.prev, None); 370 | assert_eq!(entry_1.next, None); 371 | assert_eq!(entry_1.value, 4); 372 | assert_eq!(entry_1.owner, list.head.unwrap()); 373 | 374 | unsafe { 375 | drop(Box::from_raw(entry_0_ptr)); 376 | drop(Box::from_raw(entry_1_ptr)); 377 | } 378 | } 379 | 380 | #[test] 381 | fn init_front_empty() { 382 | let mut list = init_list(); 383 | let front_node = list.init_front(); 384 | assert_eq!(list.head, Some(front_node)); 385 | assert_eq!(list.len(), 1); 386 | 387 | let front_node = unsafe { front_node.as_ref() }; 388 | assert_eq!(front_node.prev, None); 389 | assert_eq!(front_node.next, None); 390 | } 391 | 392 | #[test] 393 | fn init_front_non_empty() { 394 | let mut list = init_list(); 395 | 396 | let back_node = list.init_front(); 397 | assert_eq!(list.head, Some(back_node)); 398 | assert_eq!(list.len(), 1); 399 | { 400 | let back_node = unsafe { back_node.as_ref() }; 401 | assert_eq!(back_node.prev, None); 402 | assert_eq!(back_node.next, None); 403 | } 404 | 405 | let middle_node = list.init_front(); 406 | assert_eq!(list.head, Some(middle_node)); 407 | assert_eq!(list.len(), 2); 408 | { 409 | // validate middle node connections 410 | let middle_node = unsafe { middle_node.as_ref() }; 411 | assert_eq!(middle_node.prev, None); 412 | assert_eq!(middle_node.next, Some(back_node)); 413 | } 414 | { 415 | // validate back node connections 416 | let back_node = unsafe { back_node.as_ref() }; 417 | assert_eq!(back_node.prev, Some(middle_node)); 418 | assert_eq!(back_node.next, None); 419 | } 420 | 421 | let front_node = list.init_front(); 422 | assert_eq!(list.head, Some(front_node)); 423 | assert_eq!(list.len(), 3); 424 | { 425 | // validate front node connections 426 | let front_node = unsafe { front_node.as_ref() }; 427 | assert_eq!(front_node.prev, None); 428 | assert_eq!(front_node.next, Some(middle_node)); 429 | } 430 | 431 | { 432 | // validate middle node connections 433 | let middle_node = unsafe { middle_node.as_ref() }; 434 | assert_eq!(middle_node.prev, Some(front_node)); 435 | assert_eq!(middle_node.next, Some(back_node)); 436 | } 437 | { 438 | // validate back node connections 439 | let back_node = unsafe { back_node.as_ref() }; 440 | assert_eq!(back_node.prev, Some(middle_node)); 441 | assert_eq!(back_node.next, None); 442 | } 443 | } 444 | 445 | #[test] 446 | fn update_removes_empty_node() { 447 | let mut list = init_list(); 448 | let entry = list.insert(Rc::new(1), 2); 449 | 450 | list.update(entry); 451 | assert_eq!(unsafe { list.head.unwrap().as_ref() }.frequency, 1); 452 | list.update(entry); 453 | assert_eq!(unsafe { list.head.unwrap().as_ref() }.frequency, 2); 454 | 455 | // unleak entry 456 | unsafe { Box::from_raw(entry.as_ptr()) }; 457 | } 458 | 459 | #[test] 460 | fn update_does_not_remove_non_empty_node() { 461 | let mut list = init_list(); 462 | let entry_0 = list.insert(Rc::new(1), 2); 463 | let entry_1 = list.insert(Rc::new(3), 4); 464 | 465 | list.update(entry_0); 466 | assert_eq!(unsafe { list.head.unwrap().as_ref() }.frequency, 0); 467 | assert_eq!(list.frequencies().collect::>(), vec![0, 1]); 468 | list.update(entry_1); 469 | list.update(entry_0); 470 | assert_eq!(unsafe { list.head.unwrap().as_ref() }.frequency, 1); 471 | assert_eq!(list.frequencies().collect::>(), vec![1, 2]); 472 | 473 | // unleak entry 474 | unsafe { Box::from_raw(entry_0.as_ptr()) }; 475 | unsafe { Box::from_raw(entry_1.as_ptr()) }; 476 | } 477 | 478 | #[test] 479 | fn update_correctly_removes_in_middle_nodes() { 480 | let mut list = init_list(); 481 | let entry_0 = list.insert(Rc::new(1), 2); 482 | let entry_1 = list.insert(Rc::new(3), 4); 483 | 484 | list.update(entry_0); 485 | assert_eq!(unsafe { list.head.unwrap().as_ref() }.frequency, 0); 486 | assert_eq!(list.frequencies().collect::>(), vec![0, 1]); 487 | list.update(entry_0); 488 | assert_eq!(unsafe { list.head.unwrap().as_ref() }.frequency, 0); 489 | assert_eq!(list.frequencies().collect::>(), vec![0, 2]); 490 | 491 | // unleak entry 492 | unsafe { Box::from_raw(entry_0.as_ptr()) }; 493 | unsafe { Box::from_raw(entry_1.as_ptr()) }; 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /src/lfu/lfu_entry.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::hash::Hash; 3 | use std::ptr::NonNull; 4 | use std::rc::Rc; 5 | 6 | use super::Node; 7 | 8 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 9 | pub(super) struct LfuEntry { 10 | // We still need to keep a linked list implementation for O(1) 11 | // in-the-middle removal. 12 | pub(super) next: Option>, 13 | pub(super) prev: Option>, 14 | /// Instead of traversing up to the frequency node, we just keep a reference 15 | /// to the owning node. This ensures that entry movement is an O(1) 16 | /// operation. 17 | pub(super) owner: NonNull>, 18 | /// We need to maintain a pointer to the key as we need to remove the 19 | /// lookup table entry on lru popping, and we need the key to properly fetch 20 | /// the correct entry (the hash itself is not guaranteed to return the 21 | /// correct entry). 22 | pub(super) key: Rc, 23 | pub(super) value: Value, 24 | } 25 | 26 | #[cfg(not(tarpaulin_include))] 27 | impl Display for LfuEntry { 28 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 29 | write!(f, "{}", self.value) 30 | } 31 | } 32 | 33 | impl LfuEntry { 34 | /// Fully detaches a [`LfuEntry`] entry, removing all references to and from 35 | /// it and deallocating its memory address. 36 | /// 37 | /// This function should only be used when fully removing the item from the 38 | /// cache. [`detach`] should be used instead if it will be 39 | /// re-attached into the data structure. 40 | /// 41 | /// [`detach`]: Self::detach 42 | pub(super) fn detach_owned(node: NonNull) -> Detached { 43 | std::mem::forget(Self::detach(node)); 44 | let detached = unsafe { Box::from_raw(node.as_ptr()) }; 45 | Detached { 46 | key: detached.key, 47 | value: detached.value, 48 | } 49 | } 50 | 51 | /// Removes all references to and from the provided [`LfuEntry`], without 52 | /// actually deallocating the memory. 53 | /// 54 | /// This is useful to avoid deallocating memory and immediately 55 | /// reallocating, such as in the common operation of moving a [`LfuEntry`] 56 | /// to the next frequency node. 57 | pub(super) fn detach(mut node: NonNull) -> DetachedRef { 58 | // There are five links to fix: 59 | // ┌──────┐ (1) ┌─────┐ (2) ┌──────┐ 60 | // │ ├────►│ ├────►│ │ 61 | // │ prev │ │ cur │ │ next │ 62 | // │ │◄────┤ │◄────┤ │ 63 | // └──────┘ (3) └──┬──┘ (4) └──────┘ 64 | // │ 65 | // │ ┌───────┐ 66 | // │ (5) │ │ 67 | // └──────►│ owner │ 68 | // │ │ 69 | // └───────┘ 70 | 71 | let node_ref = unsafe { node.as_mut() }; 72 | if let Some(mut prev) = node_ref.prev { 73 | // Fix (1) 74 | unsafe { prev.as_mut().next = node_ref.next }; 75 | } 76 | 77 | if let Some(mut next) = node_ref.next { 78 | // Fix (4) 79 | unsafe { next.as_mut().prev = node_ref.prev }; 80 | } 81 | 82 | // These are probably not needed but are probably a good idea to do 83 | // anyways to have a simpler model to work with. 84 | 85 | // Fix (2) 86 | node_ref.next = None; 87 | // Fix (3) 88 | node_ref.prev = None; 89 | // Fix (5) 90 | node_ref.owner = NonNull::dangling(); 91 | 92 | DetachedRef(node) 93 | } 94 | } 95 | 96 | /// Wrapper newtype pattern representing a temporarily detached [`LfuEntry`]. 97 | /// 98 | /// A detached LFU entry is guaranteed to not be internally pointing to 99 | /// anything. Obtaining a detached LFU entry is also guaranteed to fix any 100 | /// neighbors that might be pointing to it. 101 | /// 102 | /// Unlike [`Detached`], this does not deallocate the memory associated with 103 | /// the [`LfuEntry`]. Instead, this is an optimization for reusing the detached 104 | /// [`LfuEntry`] at some point after. 105 | /// 106 | /// # Panics 107 | /// 108 | /// Because this is intended as an optimization, not re-attaching the detached 109 | /// value will likely lead to a memory leak. As a result, this intentionally 110 | /// panics to avoid this scenario. 111 | #[must_use] 112 | pub struct DetachedRef(NonNull>); 113 | 114 | impl Drop for DetachedRef { 115 | fn drop(&mut self) { 116 | panic!("Detached reference was dropped. You should re-attach it or use std::mem::forget"); 117 | } 118 | } 119 | 120 | impl DetachedRef { 121 | pub(super) fn attach_ref( 122 | self, 123 | prev: Option>>, 124 | next: Option>>, 125 | owner: NonNull>, 126 | ) -> NonNull> { 127 | // There are five links to fix: 128 | // ┌──────┐ (1) ┌─────┐ (2) ┌──────┐ 129 | // │ ├────►│ ├────►│ │ 130 | // │ prev │ │ cur │ │ next │ 131 | // │ │◄────┤ │◄────┤ │ 132 | // └──────┘ (3) └──┬──┘ (4) └──────┘ 133 | // │ 134 | // │ ┌───────┐ 135 | // │ (5) │ │ 136 | // └──────►│ owner │ 137 | // │ │ 138 | // └───────┘ 139 | 140 | let mut node = self.0; 141 | let node_ref = unsafe { node.as_mut() }; 142 | node_ref.next = next; // Fix (2) 143 | node_ref.prev = prev; // Fix (3) 144 | node_ref.owner = owner; // Fix (5) 145 | 146 | if let Some(mut prev) = unsafe { node.as_mut() }.prev { 147 | // Fixes (1) 148 | unsafe { prev.as_mut() }.next = Some(node); 149 | } 150 | 151 | if let Some(mut next) = unsafe { node.as_mut() }.next { 152 | // Fixes (4) 153 | unsafe { next.as_mut() }.prev = Some(node); 154 | } 155 | 156 | // DetachedRef explicitly has a drop impl that panics if we don't 157 | // explicitly forget about it. 158 | std::mem::forget(self); 159 | node 160 | } 161 | } 162 | 163 | /// Wrapper newtype pattern representing a detached [`LfuEntry`]. 164 | /// 165 | /// A detached LFU entry is guaranteed to not be internally pointing to 166 | /// anything. Obtaining a detached LFU entry is also guaranteed to fix any 167 | /// neighbors that might be pointing to it. 168 | #[must_use] 169 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone)] 170 | pub(super) struct Detached { 171 | pub(super) key: Rc, 172 | pub(super) value: Value, 173 | } 174 | 175 | impl Detached { 176 | pub fn new(key: Rc, value: Value) -> Self { 177 | Self { key, value } 178 | } 179 | 180 | pub fn attach( 181 | self, 182 | prev: Option>>, 183 | next: Option>>, 184 | owner: NonNull>, 185 | ) -> NonNull> { 186 | // There are five links to fix: 187 | // ┌──────┐ (1) ┌─────┐ (2) ┌──────┐ 188 | // │ ├────►│ ├────►│ │ 189 | // │ prev │ │ cur │ │ next │ 190 | // │ │◄────┤ │◄────┤ │ 191 | // └──────┘ (3) └──┬──┘ (4) └──────┘ 192 | // │ 193 | // │ ┌───────┐ 194 | // │ (5) │ │ 195 | // └──────►│ owner │ 196 | // │ │ 197 | // └───────┘ 198 | 199 | let new_node = LfuEntry { 200 | next, // Fixes (2) 201 | prev, // Fixes (3) 202 | owner, // Fixes (5) 203 | key: self.key, 204 | value: self.value, 205 | }; 206 | 207 | let leaked = Box::leak(Box::new(new_node)); 208 | let mut non_null = NonNull::from(leaked); 209 | 210 | if let Some(mut next) = unsafe { non_null.as_mut() }.next { 211 | // Fixes (4) 212 | unsafe { next.as_mut() }.prev = Some(non_null); 213 | } 214 | 215 | if let Some(mut prev) = unsafe { non_null.as_mut() }.prev { 216 | // Fixes (1) 217 | unsafe { prev.as_mut() }.next = Some(non_null); 218 | } 219 | non_null 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/lfu/mod.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use std::collections::hash_map; 3 | use std::collections::HashMap; 4 | use std::fmt::{Debug, Formatter}; 5 | use std::hash::Hash; 6 | use std::hint::unreachable_unchecked; 7 | use std::iter::{FromIterator, FusedIterator}; 8 | use std::num::NonZeroUsize; 9 | use std::ptr::NonNull; 10 | use std::rc::Rc; 11 | 12 | use crate::{Entry, LfuCacheIter}; 13 | 14 | pub(self) use freq_list::FrequencyList; 15 | pub(self) use lfu_entry::LfuEntry; 16 | pub(self) use node::Node; 17 | 18 | use self::entry::{OccupiedEntry, VacantEntry}; 19 | use self::node::WithFrequency; 20 | 21 | pub mod entry; 22 | mod freq_list; 23 | mod lfu_entry; 24 | mod node; 25 | mod util; 26 | 27 | /// A collection that if limited to a certain capacity will evict based on the 28 | /// least recently used value. 29 | // Note that Default is _not_ implemented. This is intentional, as most people 30 | // likely don't want an unbounded LFU cache by default. 31 | #[derive(Eq, PartialEq)] 32 | // This is re-exported at the crate root, so this lint can be safely ignored. 33 | #[allow(clippy::module_name_repetitions)] 34 | pub struct LfuCache { 35 | lookup: LookupMap, 36 | freq_list: FrequencyList, 37 | capacity: Option, 38 | len: usize, 39 | } 40 | 41 | #[derive(Eq, PartialEq)] 42 | struct LookupMap(HashMap, NonNull>>); 43 | 44 | #[cfg(not(tarpaulin_include))] 45 | impl Debug for LookupMap { 46 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 47 | let mut dbg = f.debug_struct("LookupMap"); 48 | for (key, value) in &self.0 { 49 | dbg.field(&format!("{:?}", key), &unsafe { 50 | value.as_ref().owner.as_ref().frequency 51 | }); 52 | } 53 | dbg.finish() 54 | } 55 | } 56 | 57 | unsafe impl Send for LfuCache {} 58 | unsafe impl Sync for LfuCache {} 59 | 60 | impl Drop for LookupMap { 61 | fn drop(&mut self) { 62 | for (_, v) in self.0.drain() { 63 | unsafe { Box::from_raw(v.as_ptr()) }; 64 | } 65 | } 66 | } 67 | 68 | impl LfuCache { 69 | /// Creates a LFU cache with at least the specified capacity. 70 | /// 71 | /// When the capacity is reached, then the least frequently used item is 72 | /// evicted. If there are multiple least frequently used items in this 73 | /// collection, the most recently added item is evicted. 74 | /// 75 | /// ``` 76 | /// # use lfu_cache::LfuCache; 77 | /// let mut cache = LfuCache::with_capacity(2); 78 | /// 79 | /// // Fill up the cache. 80 | /// cache.insert("foo", 3); 81 | /// cache.insert("bar", 4); 82 | /// 83 | /// // Insert returns the evicted value, if a value was evicted. 84 | /// let maybe_evicted = cache.insert("baz", 5); 85 | /// 86 | /// // In the case of a tie, the most recently added value is evicted. 87 | /// assert!(cache.get(&"bar").is_none()); 88 | /// assert_eq!(maybe_evicted, Some(4)); 89 | /// 90 | /// cache.get(&"baz"); 91 | /// // Otherwise, the least frequently value is evicted. 92 | /// assert_eq!(cache.pop_lfu(), Some(3)); 93 | /// ``` 94 | #[inline] 95 | #[must_use] 96 | pub fn with_capacity(capacity: usize) -> Self { 97 | Self { 98 | lookup: LookupMap(HashMap::with_capacity(capacity)), 99 | freq_list: FrequencyList::new(), 100 | capacity: NonZeroUsize::new(capacity), 101 | len: 0, 102 | } 103 | } 104 | 105 | /// Creates a LFU cache with no bound. 106 | /// 107 | /// This effectively turns the LFU cache into a very expensive [`HashMap`] 108 | /// if the least frequently used item is never removed. This is useful if 109 | /// you want to have fine-grain control over when the LFU cache should 110 | /// evict. If a LFU cache was constructed using this function, users should 111 | /// call [`pop_lfu`] to remove the least frequently used item. 112 | /// 113 | /// Construction of this cache will not heap allocate. 114 | /// 115 | /// [`pop_lfu`]: Self::pop_lfu 116 | #[inline] 117 | #[must_use] 118 | pub fn unbounded() -> Self { 119 | Self::with_capacity(0) 120 | } 121 | 122 | /// Sets the new capacity. 123 | /// 124 | /// If the provided capacity is zero, then this will turn the cache into an 125 | /// unbounded cache. If the new capacity is less than the current capacity, 126 | /// the least frequently used items are evicted until the number of items is 127 | /// equal to the capacity. 128 | /// 129 | /// If the cache becomes unbounded, then users must manually call 130 | /// [`pop_lfu`] to remove the least frequently used item. 131 | /// 132 | /// If the cache was previously unbounded and is provided a non-zero 133 | /// capacity, then the cache now is bounded and will automatically remove 134 | /// the least frequently used item when the capacity is reached. 135 | /// 136 | /// [`pop_lfu`]: Self::pop_lfu 137 | pub fn set_capacity(&mut self, new_capacity: usize) { 138 | self.capacity = NonZeroUsize::new(new_capacity); 139 | 140 | if let Some(capacity) = self.capacity { 141 | while self.len > capacity.get() { 142 | self.pop_lfu(); 143 | } 144 | } 145 | } 146 | 147 | /// Inserts a key-value pair into the cache. 148 | /// 149 | /// If the key already exists, the value is updated without updating the 150 | /// key and the previous value is returned. The frequency of this item is 151 | /// reset. 152 | /// 153 | /// If the key did not already exist, then what is returned depends on the 154 | /// capacity of the cache. If an entry was evicted, the old value is 155 | /// returned. If the cache did not need to evict an entry or was unbounded, 156 | /// this returns [None]. 157 | // TODO: return a (Key, Value, Freq) 158 | #[inline] 159 | pub fn insert(&mut self, key: Key, value: Value) -> Option { 160 | self.insert_rc(Rc::new(key), value) 161 | } 162 | 163 | /// Like [`Self::insert`], but with an shared key instead. 164 | pub(crate) fn insert_rc(&mut self, key: Rc, value: Value) -> Option { 165 | let mut evicted = self.remove(&key); 166 | 167 | if let Some(capacity) = self.capacity { 168 | // This never gets called if we had to evict an old value. 169 | if capacity.get() <= self.len { 170 | evicted = self.pop_lfu(); 171 | } 172 | } 173 | 174 | // Since an entry has a reference to its key, we've created a situation 175 | // where we have self-referential data. We can't construct the entry 176 | // before inserting it into the lookup table because the key may be 177 | // moved when inserting it (so the memory address may become invalid) 178 | // but we can't insert the entry without constructing the value first. 179 | // 180 | // As a result, we need to break this loop by performing the following: 181 | // - Insert an entry into the lookup mapping that points to a dangling 182 | // pointer. 183 | // - Obtain the _moved_ key pointer from the raw entry API 184 | // - Use this key pointer as the pointer for the entry, and overwrite 185 | // the dangling pointer with an actual value. 186 | self.lookup.0.insert(Rc::clone(&key), NonNull::dangling()); 187 | let v = self.lookup.0.get_mut(&key).unwrap(); 188 | *v = self.freq_list.insert(key, value); 189 | 190 | self.len += 1; 191 | evicted 192 | } 193 | 194 | /// Gets a value and increments the internal frequency counter of that 195 | /// value, if it exists. 196 | #[inline] 197 | pub fn get(&mut self, key: &Key) -> Option<&Value> { 198 | let entry = self.lookup.0.get_mut(key)?; 199 | self.freq_list.update(*entry); 200 | // SAFETY: This is fine because self is uniquely borrowed 201 | Some(&unsafe { entry.as_ref() }.value) 202 | } 203 | 204 | /// Like [`Self::get`], but also returns the Rc as well. 205 | pub(crate) fn get_rc_key_value(&mut self, key: &Key) -> Option<(Rc, &Value)> { 206 | let entry = self.lookup.0.get_mut(key)?; 207 | self.freq_list.update(*entry); 208 | // SAFETY: This is fine because self is uniquely borrowed 209 | let entry = unsafe { entry.as_ref() }; 210 | Some((Rc::clone(&entry.key), &entry.value)) 211 | } 212 | 213 | /// Gets a mutable value and increments the internal frequency counter of 214 | /// that value, if it exists. 215 | #[inline] 216 | pub fn get_mut(&mut self, key: &Key) -> Option<&mut Value> { 217 | self.get_rc_key_value_mut(key).map(|(_, v)| v) 218 | } 219 | 220 | /// Like `get_mut`, but also returns the Rc as well. 221 | pub(crate) fn get_rc_key_value_mut(&mut self, key: &Key) -> Option<(Rc, &mut Value)> { 222 | let entry = self.lookup.0.get_mut(key)?; 223 | self.freq_list.update(*entry); 224 | // SAFETY: This is fine because self is uniquely borrowed 225 | let entry = unsafe { entry.as_mut() }; 226 | Some((Rc::clone(&entry.key), &mut entry.value)) 227 | } 228 | 229 | /// Removes a value from the cache by key, if it exists. 230 | #[inline] 231 | pub fn remove(&mut self, key: &Key) -> Option { 232 | self.lookup.0.remove(key).map(|node| { 233 | // SAFETY: We have unique access to self. At this point, we've 234 | // removed the entry from the lookup map but haven't removed it from 235 | // the frequency data structure, so we need to clean it up there 236 | // before returning the value. 237 | 238 | let mut freq_node = unsafe { node.as_ref() }.owner; 239 | let detached = unsafe { freq_node.as_mut() }.remove(node); 240 | 241 | // freq_node no longer is being referred to by lfu_entry 242 | 243 | if unsafe { freq_node.as_ref() }.elements.is_none() { 244 | let freq_head = unsafe { Box::from_raw(freq_node.as_ptr()) }; 245 | if self.freq_list.head == Some(NonNull::from(&*freq_head)) { 246 | self.freq_list.head = freq_head.next; 247 | } 248 | 249 | freq_head.detach(); 250 | } 251 | self.len -= 1; 252 | detached.value 253 | }) 254 | } 255 | 256 | /// Gets the given key's corresponding entry in the LFU cache for in-place 257 | /// manipulation. If the key refers to an occupied entry, that entry then is 258 | /// immediately considered accessed, even if no reading or writing is 259 | /// performed. This behavior is a limitation of the Entry API. 260 | #[inline] 261 | pub fn entry(&mut self, key: Key) -> Entry<'_, Key, Value> { 262 | let key = Rc::new(key); 263 | match self.lookup.0.entry(Rc::clone(&key)) { 264 | hash_map::Entry::Occupied(mut entry) => { 265 | self.freq_list.update(*entry.get_mut()); 266 | Entry::Occupied(OccupiedEntry::new(entry, &mut self.len)) 267 | } 268 | hash_map::Entry::Vacant(entry) => Entry::Vacant(VacantEntry::new( 269 | entry, 270 | key, 271 | &mut self.freq_list, 272 | self.capacity, 273 | &mut self.len, 274 | )), 275 | } 276 | } 277 | 278 | /// Evicts the least frequently used value and returns it. If the cache is 279 | /// empty, then this returns None. If there are multiple items that have an 280 | /// equal access count, then the most recently added value is evicted. 281 | #[inline] 282 | pub fn pop_lfu(&mut self) -> Option { 283 | self.pop_lfu_key_value_frequency().map(|(_, v, _)| v) 284 | } 285 | 286 | /// Evicts the least frequently used key-value pair and returns it. If the 287 | /// cache is empty, then this returns None. If there are multiple items that 288 | /// have an equal access count, then the most recently added key-value pair 289 | /// is evicted. 290 | #[inline] 291 | pub fn pop_lfu_key_value(&mut self) -> Option<(Key, Value)> { 292 | self.pop_lfu_key_value_frequency().map(|(k, v, _)| (k, v)) 293 | } 294 | 295 | /// Evicts the least frequently used value and returns it, the key it was 296 | /// inserted under, and the frequency it had. If the cache is empty, then 297 | /// this returns None. If there are multiple items that have an equal access 298 | /// count, then the most recently added key-value pair is evicted. 299 | #[inline] 300 | pub fn pop_lfu_key_value_frequency(&mut self) -> Option<(Key, Value, usize)> { 301 | self.freq_list 302 | .pop_lfu() 303 | .map(|WithFrequency(freq, detached)| { 304 | // SAFETY: This is fine since self is uniquely borrowed. 305 | let key = detached.key.as_ref(); 306 | self.lookup.0.remove(key); 307 | self.len -= 1; 308 | 309 | // SAFETY: entry_ptr is guaranteed to be a live reference and is 310 | // is separated from the data structure as a guarantee of pop_lfu. 311 | // As a result, at this point, we're guaranteed that we have the 312 | // only reference of entry_ptr. 313 | 314 | // false positive, lint doesn't respect match guard. 315 | #[allow(clippy::option_if_let_else)] 316 | let key = match Rc::try_unwrap(detached.key) { 317 | Ok(k) => k, 318 | Err(_) => unsafe { unreachable_unchecked() }, 319 | }; 320 | (key, detached.value, freq) 321 | }) 322 | } 323 | 324 | /// Clears the cache, returning the iterator of the previous cached values. 325 | pub fn clear(&mut self) -> LfuCacheIter { 326 | let mut to_return = Self::with_capacity(self.capacity.map_or(0, NonZeroUsize::get)); 327 | std::mem::swap(&mut to_return, self); 328 | to_return.into_iter() 329 | } 330 | 331 | /// Peeks at the next value to be evicted, if there is one. This will not 332 | /// increment the access counter for that value. 333 | #[inline] 334 | #[must_use] 335 | pub fn peek_lfu(&self) -> Option<&Value> { 336 | self.freq_list.peek_lfu() 337 | } 338 | 339 | /// Peeks at the key for the next value to be evicted, if there is one. This 340 | /// will not increment the access counter for that value. 341 | #[inline] 342 | #[must_use] 343 | pub fn peek_lfu_key(&self) -> Option<&Key> { 344 | self.freq_list.peek_lfu_key() 345 | } 346 | 347 | /// Returns the current capacity of the cache. 348 | #[inline] 349 | #[must_use] 350 | pub const fn capacity(&self) -> Option { 351 | self.capacity 352 | } 353 | 354 | /// Returns the current number of items in the cache. This is a constant 355 | /// time operation. 356 | #[inline] 357 | #[must_use] 358 | pub const fn len(&self) -> usize { 359 | self.len 360 | } 361 | 362 | /// Returns if the cache contains no elements. 363 | #[inline] 364 | #[must_use] 365 | pub const fn is_empty(&self) -> bool { 366 | self.len == 0 367 | } 368 | 369 | /// Returns if the cache is unbounded. 370 | #[inline] 371 | #[must_use] 372 | pub const fn is_unbounded(&self) -> bool { 373 | self.capacity.is_none() 374 | } 375 | 376 | /// Returns the frequencies that this cache has. This is a linear time 377 | /// operation. 378 | #[inline] 379 | #[must_use] 380 | pub fn frequencies(&self) -> Vec { 381 | // TODO: Breaking change -> return impl iterator 382 | self.freq_list.frequencies().collect() 383 | } 384 | 385 | /// Sets the capacity to the amount of objects currently in the cache. If 386 | /// no items were in the cache, the cache becomes unbounded. 387 | #[inline] 388 | pub fn shrink_to_fit(&mut self) { 389 | self.set_capacity(self.len); 390 | } 391 | 392 | /// Returns an iterator over the keys of the LFU cache in any order. 393 | #[inline] 394 | pub fn keys(&self) -> impl Iterator + FusedIterator + '_ { 395 | self.lookup.0.keys().map(Borrow::borrow) 396 | } 397 | 398 | /// Returns an iterator over the values of the LFU cache in any order. Note 399 | /// that this does **not** increment the count for any of the values. 400 | #[inline] 401 | pub fn peek_values(&self) -> impl Iterator + FusedIterator + '_ { 402 | self.lookup 403 | .0 404 | .values() 405 | .map(|value| &unsafe { value.as_ref() }.value) 406 | } 407 | 408 | /// Returns an iterator over the keys and values of the LFU cache in any 409 | /// order. Note that this does **not** increment the count for any of the 410 | /// values. 411 | #[inline] 412 | pub fn peek_iter(&self) -> impl Iterator + FusedIterator + '_ { 413 | self.lookup 414 | .0 415 | .iter() 416 | .map(|(key, value)| (key.borrow(), &unsafe { value.as_ref() }.value)) 417 | } 418 | } 419 | 420 | #[cfg(not(tarpaulin_include))] 421 | impl Debug for LfuCache { 422 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 423 | let mut dbg = f.debug_struct("LfuCache"); 424 | dbg.field("len", &self.len); 425 | dbg.field("capacity", &self.capacity); 426 | dbg.field("freq_list", &self.freq_list); 427 | dbg.field("lookup_map", &self.lookup); 428 | dbg.finish() 429 | } 430 | } 431 | 432 | impl FromIterator<(Key, Value)> for LfuCache { 433 | /// Constructs a LFU cache with the capacity equal to the number of elements 434 | /// in the iterator. 435 | fn from_iter>(iter: T) -> Self { 436 | let mut cache = Self::unbounded(); 437 | for (k, v) in iter { 438 | cache.insert(k, v); 439 | } 440 | cache.shrink_to_fit(); 441 | cache 442 | } 443 | } 444 | 445 | impl Extend<(Key, Value)> for LfuCache { 446 | /// Inserts the items from the iterator into the cache. Note that this may 447 | /// evict items if the number of elements in the iterator plus the number of 448 | /// current items in the cache exceeds the capacity of the cache. 449 | fn extend>(&mut self, iter: T) { 450 | for (k, v) in iter { 451 | self.insert(k, v); 452 | } 453 | } 454 | } 455 | 456 | impl IntoIterator for LfuCache { 457 | type Item = (Key, Value); 458 | 459 | type IntoIter = LfuCacheIter; 460 | 461 | fn into_iter(self) -> Self::IntoIter { 462 | LfuCacheIter(self) 463 | } 464 | } 465 | 466 | #[cfg(test)] 467 | mod get { 468 | use super::LfuCache; 469 | 470 | #[test] 471 | fn empty() { 472 | let mut cache = LfuCache::::unbounded(); 473 | for i in 0..100 { 474 | assert!(cache.get(&i).is_none()) 475 | } 476 | } 477 | 478 | #[test] 479 | fn get_mut() { 480 | let mut cache = LfuCache::unbounded(); 481 | cache.insert(1, 2); 482 | assert_eq!(cache.frequencies(), vec![0]); 483 | *cache.get_mut(&1).unwrap() = 3; 484 | assert_eq!(cache.frequencies(), vec![1]); 485 | assert_eq!(cache.get(&1), Some(&3)); 486 | } 487 | 488 | #[test] 489 | fn getting_is_ok_after_adding_other_value() { 490 | let mut cache = LfuCache::unbounded(); 491 | cache.insert(1, 2); 492 | assert_eq!(cache.get(&1), Some(&2)); 493 | cache.insert(3, 4); 494 | assert_eq!(cache.get(&1), Some(&2)); 495 | } 496 | 497 | #[test] 498 | fn bounded_alternating_values() { 499 | let mut cache = LfuCache::with_capacity(8); 500 | cache.insert(1, 1); 501 | cache.insert(2, 2); 502 | for _ in 0..100 { 503 | cache.get(&1); 504 | cache.get(&2); 505 | } 506 | 507 | assert_eq!(cache.len(), 2); 508 | assert_eq!(cache.frequencies(), vec![100]); 509 | } 510 | } 511 | 512 | #[cfg(test)] 513 | mod insert { 514 | use super::LfuCache; 515 | 516 | #[test] 517 | fn insert_unbounded() { 518 | let mut cache = LfuCache::unbounded(); 519 | 520 | for i in 0..100 { 521 | cache.insert(i, i + 100); 522 | } 523 | 524 | for i in 0..100 { 525 | assert_eq!(cache.get(&i), Some(&(i + 100))); 526 | assert!(cache.get(&(i + 100)).is_none()); 527 | } 528 | } 529 | 530 | #[test] 531 | fn reinsertion_of_same_key_resets_freq() { 532 | let mut cache = LfuCache::unbounded(); 533 | cache.insert(1, 1); 534 | cache.get(&1); 535 | cache.insert(1, 1); 536 | assert_eq!(cache.frequencies(), vec![0]); 537 | } 538 | 539 | #[test] 540 | fn insert_bounded() { 541 | let mut cache = LfuCache::with_capacity(20); 542 | 543 | for i in 0..100 { 544 | cache.insert(i, i + 100); 545 | } 546 | } 547 | 548 | #[test] 549 | fn insert_returns_evicted() { 550 | let mut cache = LfuCache::with_capacity(1); 551 | assert_eq!(cache.insert(1, 2), None); 552 | for _ in 0..10 { 553 | assert_eq!(cache.insert(3, 4), Some(2)); 554 | assert_eq!(cache.insert(1, 2), Some(4)); 555 | } 556 | } 557 | } 558 | 559 | #[cfg(test)] 560 | mod pop { 561 | use super::LfuCache; 562 | 563 | #[test] 564 | fn pop() { 565 | let mut cache = LfuCache::unbounded(); 566 | for i in 0..100 { 567 | cache.insert(i, i + 100); 568 | } 569 | 570 | for i in 0..100 { 571 | assert_eq!(cache.lookup.0.len(), 100 - i); 572 | assert_eq!(cache.pop_lfu(), Some(200 - i - 1)); 573 | } 574 | } 575 | 576 | #[test] 577 | fn pop_empty() { 578 | let mut cache = LfuCache::::unbounded(); 579 | assert_eq!(None, cache.pop_lfu()); 580 | assert_eq!(None, cache.pop_lfu_key_value()); 581 | } 582 | 583 | #[test] 584 | fn set_capacity_evicts_multiple() { 585 | let mut cache = LfuCache::unbounded(); 586 | for i in 0..100 { 587 | cache.insert(i, i + 100); 588 | } 589 | cache.set_capacity(10); 590 | assert_eq!(cache.len(), 10); 591 | } 592 | 593 | #[test] 594 | fn pop_multiple_varying_frequencies() { 595 | let mut cache = LfuCache::unbounded(); 596 | for i in 0..10 { 597 | cache.insert(i, i + 10); 598 | } 599 | for i in 0..10 { 600 | for _ in 0..i * i { 601 | cache.get(&i).unwrap(); 602 | } 603 | } 604 | for i in 0..10 { 605 | assert_eq!(10 - i, cache.len()); 606 | assert_eq!(Some(i + 10), cache.pop_lfu()); 607 | } 608 | } 609 | } 610 | 611 | #[cfg(test)] 612 | mod remove { 613 | use super::LfuCache; 614 | 615 | #[test] 616 | fn remove_to_empty() { 617 | let mut cache = LfuCache::unbounded(); 618 | cache.insert(1, 2); 619 | assert_eq!(cache.remove(&1), Some(2)); 620 | 621 | assert!(cache.is_empty()); 622 | assert_eq!(cache.freq_list.len(), 0); 623 | } 624 | 625 | #[test] 626 | fn remove_empty() { 627 | let mut cache = LfuCache::::unbounded(); 628 | assert!(cache.remove(&1).is_none()); 629 | } 630 | 631 | #[test] 632 | fn remove_to_nonempty() { 633 | let mut cache = LfuCache::unbounded(); 634 | cache.insert(1, 2); 635 | cache.insert(3, 4); 636 | 637 | assert_eq!(cache.remove(&1), Some(2)); 638 | 639 | assert!(!cache.is_empty()); 640 | 641 | assert_eq!(cache.remove(&3), Some(4)); 642 | 643 | assert!(cache.is_empty()); 644 | assert_eq!(cache.freq_list.len(), 0); 645 | } 646 | 647 | #[test] 648 | fn remove_middle() { 649 | let mut cache = LfuCache::unbounded(); 650 | cache.insert(1, 2); 651 | cache.insert(3, 4); 652 | cache.insert(5, 6); 653 | cache.insert(7, 8); 654 | cache.insert(9, 10); 655 | cache.insert(11, 12); 656 | 657 | cache.get(&7); 658 | cache.get(&9); 659 | cache.get(&11); 660 | 661 | assert_eq!(cache.frequencies(), vec![0, 1]); 662 | assert_eq!(cache.len(), 6); 663 | 664 | cache.remove(&9); 665 | assert!(cache.get(&7).is_some()); 666 | assert!(cache.get(&11).is_some()); 667 | 668 | cache.remove(&3); 669 | assert!(cache.get(&1).is_some()); 670 | assert!(cache.get(&5).is_some()); 671 | } 672 | 673 | #[test] 674 | fn remove_end() { 675 | let mut cache = LfuCache::unbounded(); 676 | cache.insert(1, 2); 677 | cache.insert(3, 4); 678 | cache.insert(5, 6); 679 | cache.insert(7, 8); 680 | cache.insert(9, 10); 681 | cache.insert(11, 12); 682 | 683 | cache.get(&7); 684 | cache.get(&9); 685 | cache.get(&11); 686 | 687 | assert_eq!(cache.frequencies(), vec![0, 1]); 688 | assert_eq!(cache.len(), 6); 689 | 690 | cache.remove(&7); 691 | assert!(cache.get(&9).is_some()); 692 | assert!(cache.get(&11).is_some()); 693 | 694 | cache.remove(&1); 695 | assert!(cache.get(&3).is_some()); 696 | assert!(cache.get(&5).is_some()); 697 | } 698 | 699 | #[test] 700 | fn remove_start() { 701 | let mut cache = LfuCache::unbounded(); 702 | cache.insert(1, 2); 703 | cache.insert(3, 4); 704 | cache.insert(5, 6); 705 | cache.insert(7, 8); 706 | cache.insert(9, 10); 707 | cache.insert(11, 12); 708 | 709 | cache.get(&7); 710 | cache.get(&9); 711 | cache.get(&11); 712 | 713 | assert_eq!(cache.frequencies(), vec![0, 1]); 714 | assert_eq!(cache.len(), 6); 715 | 716 | cache.remove(&11); 717 | assert!(cache.get(&9).is_some()); 718 | assert!(cache.get(&7).is_some()); 719 | 720 | cache.remove(&5); 721 | assert!(cache.get(&3).is_some()); 722 | assert!(cache.get(&1).is_some()); 723 | } 724 | 725 | #[test] 726 | fn remove_connects_next_owner() { 727 | let mut cache = LfuCache::unbounded(); 728 | cache.insert(1, 1); 729 | cache.insert(2, 2); 730 | assert_eq!(cache.get(&1), Some(&1)); 731 | assert_eq!(cache.remove(&2), Some(2)); 732 | assert_eq!(cache.get(&1), Some(&1)); 733 | } 734 | } 735 | 736 | #[cfg(test)] 737 | mod bookkeeping { 738 | use std::num::NonZeroUsize; 739 | 740 | use super::LfuCache; 741 | 742 | #[test] 743 | fn getting_one_element_has_constant_freq_list_size() { 744 | let mut cache = LfuCache::unbounded(); 745 | cache.insert(1, 2); 746 | assert_eq!(cache.freq_list.len(), 1); 747 | 748 | for _ in 0..100 { 749 | cache.get(&1); 750 | assert_eq!(cache.freq_list.len(), 1); 751 | } 752 | } 753 | 754 | #[test] 755 | fn freq_list_node_merges() { 756 | let mut cache = LfuCache::unbounded(); 757 | cache.insert(1, 2); 758 | cache.insert(3, 4); 759 | assert_eq!(cache.freq_list.len(), 1); 760 | assert!(cache.get(&1).is_some()); 761 | assert_eq!(cache.freq_list.len(), 2); 762 | assert!(cache.get(&3).is_some()); 763 | assert_eq!(cache.freq_list.len(), 1); 764 | } 765 | 766 | #[test] 767 | fn freq_list_multi_items() { 768 | let mut cache = LfuCache::unbounded(); 769 | cache.insert(1, 2); 770 | cache.get(&1); 771 | cache.get(&1); 772 | cache.insert(3, 4); 773 | assert_eq!(cache.freq_list.len(), 2); 774 | cache.get(&3); 775 | assert_eq!(cache.freq_list.len(), 2); 776 | cache.get(&3); 777 | assert_eq!(cache.freq_list.len(), 1); 778 | } 779 | 780 | #[test] 781 | fn unbounded_is_unbounded() { 782 | assert!(LfuCache::::unbounded().is_unbounded()); 783 | assert!(!LfuCache::::with_capacity(3).is_unbounded()); 784 | } 785 | 786 | #[test] 787 | fn capacity_reports_internal_capacity() { 788 | assert_eq!(LfuCache::::unbounded().capacity(), None); 789 | assert_eq!( 790 | LfuCache::::with_capacity(3).capacity(), 791 | Some(NonZeroUsize::new(3).unwrap()) 792 | ); 793 | } 794 | 795 | #[test] 796 | fn clear_is_ok() { 797 | let mut cache = LfuCache::unbounded(); 798 | for i in 0..10 { 799 | cache.insert(i, i); 800 | } 801 | 802 | assert!(!cache.is_empty()); 803 | 804 | cache.clear(); 805 | 806 | assert!(cache.is_empty()); 807 | 808 | for i in 0..10 { 809 | assert!(cache.get(&i).is_none()); 810 | } 811 | } 812 | } 813 | -------------------------------------------------------------------------------- /src/lfu/node.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{Hash, Hasher}; 2 | use std::ptr::NonNull; 3 | 4 | use super::lfu_entry::{Detached, DetachedRef}; 5 | use super::LfuEntry; 6 | 7 | #[derive(Default, Eq, Ord, PartialOrd, Debug)] 8 | pub(super) struct Node { 9 | pub(super) next: Option>, 10 | pub(super) prev: Option>, 11 | pub(super) elements: Option>>, 12 | pub(super) frequency: usize, 13 | } 14 | 15 | impl PartialEq for Node { 16 | fn eq(&self, other: &Self) -> bool { 17 | self.frequency == other.frequency 18 | } 19 | } 20 | 21 | #[cfg(not(tarpaulin_include))] 22 | impl Hash for Node { 23 | fn hash(&self, state: &mut H) { 24 | state.write_usize(self.frequency); 25 | } 26 | } 27 | 28 | impl Drop for Node { 29 | fn drop(&mut self) { 30 | // Note that we do _NOT_ drop the elements field. That field should be 31 | // managed by the lookup table, and thus freeing them results in a 32 | // double free. 33 | if let Some(ptr) = self.next { 34 | // SAFETY: self is exclusively accessed 35 | unsafe { Box::from_raw(ptr.as_ptr()) }; 36 | } 37 | } 38 | } 39 | 40 | impl Node { 41 | pub(super) fn create_increment(mut node: NonNull) -> NonNull { 42 | // There are four links to fix: 43 | // ┌─────┐ (1) ┌─────┐ (2) ┌──────┐ 44 | // │ ├────►│ ├────►│ │ 45 | // │ cur │ │ new │ │ next │ 46 | // │ │◄────┤ │◄────┤ │ 47 | // └─────┘ (3) └─────┘ (4) └──────┘ 48 | 49 | // Initialize new node with links to current and next's next 50 | let new_node = Box::new(Self { 51 | next: unsafe { node.as_ref() }.next, // Fixes (2) 52 | prev: Some(node), // Fixes (3) 53 | elements: None, 54 | frequency: unsafe { node.as_ref() }.frequency + 1, 55 | }); 56 | 57 | // Fix links to point to new node 58 | let new_node: NonNull<_> = Box::leak(new_node).into(); 59 | 60 | // Fix next element's previous reference to new node 61 | if let Some(mut next_node) = unsafe { node.as_ref() }.next { 62 | // SAFETY: self is exclusively accessed 63 | let node_ptr = unsafe { next_node.as_mut() }; 64 | node_ptr.prev = Some(new_node); 65 | } 66 | // Fix current element's next reference to new node 67 | unsafe { node.as_mut() }.next = Some(new_node); 68 | 69 | new_node 70 | } 71 | 72 | /// Pushes the entry to the front of the list 73 | pub(super) fn push( 74 | mut node: NonNull, 75 | entry: Detached, 76 | ) -> NonNull> { 77 | let attached = entry.attach(None, unsafe { node.as_mut() }.elements, node); 78 | unsafe { node.as_mut() }.elements = Some(attached); 79 | attached 80 | } 81 | 82 | pub(super) fn push_ref(mut node: NonNull, entry: DetachedRef) { 83 | let attached = entry.attach_ref(None, unsafe { node.as_mut() }.elements, node); 84 | unsafe { node.as_mut() }.elements = Some(attached); 85 | } 86 | 87 | pub(super) fn pop(&mut self) -> Option>> { 88 | let node_ptr = self.elements?; 89 | // let elements = unsafe { Box::from_raw(node_ptr.as_ptr()) }; 90 | self.elements = unsafe { node_ptr.as_ref() }.next; 91 | let detached = LfuEntry::detach_owned(node_ptr); 92 | Some(WithFrequency(self.frequency, detached)) 93 | } 94 | 95 | pub(super) fn remove(&mut self, entry: NonNull>) -> Detached { 96 | let head = self.elements.unwrap(); 97 | if head == entry { 98 | self.elements = unsafe { head.as_ref() }.next; 99 | } 100 | 101 | LfuEntry::detach_owned(entry) 102 | } 103 | 104 | pub(super) fn remove_ref(&mut self, entry: NonNull>) -> DetachedRef { 105 | let head = self.elements.unwrap(); 106 | if head == entry { 107 | self.elements = unsafe { head.as_ref() }.next; 108 | } 109 | 110 | LfuEntry::detach(entry) 111 | } 112 | 113 | pub(super) fn detach(mut self) { 114 | assert!(self.elements.is_none()); 115 | // There are four links to fix: 116 | // ┌──────┐ (1) ┌─────┐ (2) ┌──────┐ 117 | // │ ├────►│ ├────►│ │ 118 | // │ prev │ │ cur │ │ next │ 119 | // │ │◄────┤ │◄────┤ │ 120 | // └──────┘ (3) └─────┘ (4) └──────┘ 121 | 122 | if let Some(mut prev) = self.prev { 123 | unsafe { prev.as_mut() }.next = self.next; // Fixes (1) 124 | } 125 | 126 | if let Some(mut next) = self.next { 127 | unsafe { next.as_mut() }.prev = self.prev; // Fixes (4) 128 | } 129 | 130 | self.next = None; 131 | self.prev = None; 132 | } 133 | 134 | pub(super) fn peek(&self) -> Option<&T> { 135 | Some(&unsafe { self.elements?.as_ref() }.value) 136 | } 137 | 138 | pub(super) fn peek_key(&self) -> Option<&Key> { 139 | Some(&unsafe { self.elements?.as_ref() }.key) 140 | } 141 | 142 | pub(super) fn len(&self) -> usize { 143 | let mut count = 0; 144 | let mut head = self.elements; 145 | while let Some(cur_node) = head { 146 | let cur_node = unsafe { cur_node.as_ref() }; 147 | count += 1; 148 | head = cur_node.next; 149 | } 150 | count 151 | } 152 | } 153 | 154 | #[derive(Default, PartialEq, Eq, Ord, PartialOrd, Debug)] 155 | pub(super) struct WithFrequency(pub usize, pub T); 156 | 157 | #[cfg(test)] 158 | mod node { 159 | use super::Node; 160 | use crate::lfu::lfu_entry::Detached; 161 | use crate::lfu::node::WithFrequency; 162 | use std::hash::Hash; 163 | use std::ops::{Deref, DerefMut}; 164 | use std::ptr::NonNull; 165 | use std::rc::Rc; 166 | 167 | fn init_node() -> AutoDropNode { 168 | AutoDropNode::default() 169 | } 170 | 171 | /// Normally, [`Node`] doesn't drop any of its elements. This is intentional 172 | /// as the the ownership of those nodes are handled elsewhere. This means 173 | /// for tests, we would leak memory. [`AutoDropNode`] is a newtype that 174 | /// unlike the standard behavior, drops its elements. 175 | #[derive(Default)] 176 | #[repr(transparent)] 177 | struct AutoDropNode(Node); 178 | 179 | impl Drop for AutoDropNode { 180 | fn drop(&mut self) { 181 | while let Some(_) = self.0.pop() {} 182 | } 183 | } 184 | 185 | impl Deref for AutoDropNode { 186 | type Target = Node; 187 | 188 | fn deref(&self) -> &Self::Target { 189 | &self.0 190 | } 191 | } 192 | 193 | impl DerefMut for AutoDropNode { 194 | fn deref_mut(&mut self) -> &mut Self::Target { 195 | &mut self.0 196 | } 197 | } 198 | 199 | impl PartialEq> for AutoDropNode { 200 | fn eq(&self, other: &Node) -> bool { 201 | &self.0 == other 202 | } 203 | } 204 | 205 | #[test] 206 | fn create_increment_next_empty() { 207 | unsafe { 208 | let head = init_node(); 209 | let head = Box::new(head); 210 | let mut head = NonNull::from(Box::leak(head)); 211 | Node::::create_increment(head.cast()); 212 | 213 | let next = head.as_ref().next; 214 | assert!(next.is_some()); 215 | let next = next.unwrap(); 216 | 217 | // assert links between are good. 218 | assert_eq!(next.as_ref().prev, Some(head.cast())); 219 | assert_eq!(head.as_ref().next, Some(next)); 220 | 221 | // assert links away are good 222 | assert!(head.as_ref().next.unwrap().as_ref().next.is_none()); 223 | assert!(head.as_ref().prev.is_none()); 224 | 225 | // Assert frequency is incremented 226 | assert_eq!( 227 | head.as_ref().frequency + 1, 228 | head.as_ref().next.unwrap().as_ref().frequency 229 | ); 230 | 231 | // unleak box 232 | drop(Box::from_raw(head.as_mut())); 233 | } 234 | } 235 | 236 | #[test] 237 | fn create_increment_next_non_empty() { 238 | unsafe { 239 | let head = init_node(); 240 | let head = Box::new(head); 241 | let mut head = NonNull::from(Box::leak(head)); 242 | Node::::create_increment(head.cast()); 243 | Node::::create_increment(head.cast()); 244 | 245 | let next = head.as_ref().next.unwrap(); 246 | let next_next = next.as_ref().next.unwrap(); 247 | 248 | // assert head links 249 | assert!(head.as_ref().prev.is_none()); 250 | assert_eq!(head.as_ref().next, Some(next)); 251 | 252 | // assert first ele links 253 | assert_eq!(next.as_ref().prev, Some(head.cast())); 254 | assert_eq!(next.as_ref().next, Some(next_next)); 255 | 256 | // assert second ele links 257 | assert_eq!(next_next.as_ref().prev, Some(next)); 258 | assert_eq!(next_next.as_ref().next, None); 259 | 260 | // Assert frequency is incremented 261 | assert_eq!( 262 | head.as_ref().frequency + 1, 263 | head.as_ref().next.unwrap().as_ref().frequency 264 | ); 265 | 266 | // unleak box 267 | drop(Box::from_raw(head.as_mut())); 268 | } 269 | } 270 | 271 | #[test] 272 | fn append_empty() { 273 | let mut node = init_node(); 274 | let entry = Detached::new(Rc::new(1), 2); 275 | Node::push(NonNull::from(&mut *node), entry); 276 | 277 | let head = node.elements.unwrap(); 278 | let head = unsafe { head.as_ref() }; 279 | assert_eq!(head.owner, NonNull::from(&*node)); 280 | assert_eq!(head.prev, None); 281 | assert_eq!(head.next, None); 282 | } 283 | 284 | #[test] 285 | fn append_non_empty() { 286 | let mut node = init_node(); 287 | 288 | // insert first node 289 | let entry_0 = Detached::new(Rc::new(1), 2); 290 | Node::push(NonNull::from(&mut *node), entry_0); 291 | let head_0 = unsafe { node.elements.unwrap().as_ref() }; 292 | assert_eq!(head_0.owner, NonNull::from(&*node)); 293 | assert_eq!(head_0.value, 2); 294 | assert_eq!(head_0.next, None); 295 | assert_eq!(head_0.prev, None); 296 | 297 | // insert second node 298 | let entry_1 = Detached::new(Rc::new(1), 3); 299 | Node::push(NonNull::from(&mut *node), entry_1); 300 | let head = unsafe { node.elements.unwrap().as_ref() }; 301 | assert_eq!(head.owner, NonNull::from(&*node)); 302 | assert_eq!(head.value, 3); 303 | assert_eq!(unsafe { head.next.unwrap().as_ref() }.value, 2); 304 | assert_eq!(head.prev, None); 305 | 306 | // insert last node 307 | let entry_2 = Detached::new(Rc::new(1), 4); 308 | Node::push(NonNull::from(&mut *node), entry_2); 309 | let head = unsafe { node.elements.unwrap().as_ref() }; 310 | assert_eq!(head.owner, NonNull::from(&*node)); 311 | assert_eq!(head.value, 4); 312 | assert_eq!(unsafe { head.next.unwrap().as_ref() }.value, 3); 313 | assert_eq!(head.prev, None); 314 | } 315 | 316 | #[test] 317 | fn pop_empty() { 318 | assert!(init_node().pop().is_none()); 319 | } 320 | 321 | #[test] 322 | fn pop_single() { 323 | let mut node = init_node(); 324 | 325 | let entry = Detached::new(Rc::new(1), 2); 326 | Node::push(NonNull::from(&mut *node), entry.clone()); 327 | 328 | let popped = node.pop(); 329 | assert_eq!(popped, Some(WithFrequency(0, entry))); 330 | 331 | assert!(node.elements.is_none()); 332 | } 333 | 334 | #[test] 335 | fn pop_non_empty() { 336 | let mut node = init_node(); 337 | 338 | // insert first node 339 | let entry_0 = Detached::new(Rc::new(1), 2); 340 | Node::push(NonNull::from(&mut *node), entry_0.clone()); 341 | 342 | // insert second node 343 | let entry_1 = Detached::new(Rc::new(1), 3); 344 | Node::push(NonNull::from(&mut *node), entry_1.clone()); 345 | 346 | // insert last node 347 | let entry_2 = Detached::new(Rc::new(1), 4); 348 | Node::push(NonNull::from(&mut *node), entry_2.clone()); 349 | 350 | // pop top 351 | let popped = node.pop(); 352 | assert_eq!(popped, Some(WithFrequency(0, entry_2))); 353 | 354 | // validate head 355 | let head = unsafe { node.elements.unwrap().as_ref() }; 356 | assert_eq!(head.prev, None); 357 | assert_eq!(head.value, 3); 358 | assert_eq!(unsafe { head.next.unwrap().as_ref() }.value, 2); 359 | 360 | // validate next 361 | 362 | let head = unsafe { head.next.unwrap().as_ref() }; 363 | assert_eq!(unsafe { head.prev.unwrap().as_ref() }.value, 3); 364 | assert_eq!(head.value, 2); 365 | assert_eq!(head.next, None); 366 | } 367 | 368 | #[test] 369 | fn peek_empty() { 370 | let node = init_node(); 371 | assert!(node.peek().is_none()); 372 | assert!(node.peek_key().is_none()); 373 | } 374 | 375 | #[test] 376 | fn peek_non_empty() { 377 | let mut node = init_node(); 378 | let entry = Detached::new(Rc::new(1), 2); 379 | Node::push(NonNull::from(&mut *node), entry); 380 | assert_eq!(node.peek(), Some(&2)); 381 | assert_eq!(node.peek_key(), Some(&1)); 382 | } 383 | 384 | #[test] 385 | fn len_is_consistent() { 386 | let mut node = init_node(); 387 | assert_eq!(node.len(), 0); 388 | let entry_0 = Detached::new(Rc::new(1), 2); 389 | let entry_1 = Detached::new(Rc::new(3), 4); 390 | let entry_2 = Detached::new(Rc::new(5), 6); 391 | Node::push(NonNull::from(&mut *node), entry_0); 392 | assert_eq!(node.len(), 1); 393 | Node::push(NonNull::from(&mut *node), entry_1); 394 | assert_eq!(node.len(), 2); 395 | Node::push(NonNull::from(&mut *node), entry_2); 396 | assert_eq!(node.len(), 3); 397 | node.pop(); 398 | assert_eq!(node.len(), 2); 399 | node.pop(); 400 | assert_eq!(node.len(), 1); 401 | node.pop(); 402 | assert_eq!(node.len(), 0); 403 | node.pop(); 404 | assert_eq!(node.len(), 0); 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /src/lfu/util.rs: -------------------------------------------------------------------------------- 1 | use std::{hash::Hash, ptr::NonNull}; 2 | 3 | use super::{LfuEntry, Node}; 4 | 5 | /// Removes the entry from the cache, cleaning up any values if necessary. 6 | pub(super) fn remove_entry_pointer( 7 | mut node: LfuEntry, 8 | len: &mut usize, 9 | ) -> Value 10 | where 11 | Key: Hash + Eq, 12 | { 13 | let owner = unsafe { node.owner.as_mut() }; 14 | drop(LfuEntry::detach_owned(NonNull::from(&mut node))); 15 | if owner.elements.is_none() { 16 | Node::detach(unsafe { *Box::from_raw(owner) }); 17 | 18 | // Drop the node, since the node is empty now. 19 | // TODO: low frequency count optimization, where we don't dealloc 20 | // very low frequency counts since we're likely to just realloc them 21 | // sooner than later. 22 | } 23 | *len -= 1; 24 | 25 | node.value 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic, clippy::nursery, clippy::cargo)] 2 | #![deny(missing_docs)] 3 | 4 | //! This crate provides an LRU cache with constant time insertion, fetching, 5 | //! and removing. 6 | //! 7 | //! Storage of values to this collection allocates to the heap. Clients with a 8 | //! limited heap size should avoid allocating large values and instead use some 9 | //! form of indirection to avoid a heap overflow. 10 | //! 11 | //! Performance of this LRU cache is bounded by constant time operations of a 12 | //! typical unsafe linked list on your platform. For most users, this is an 13 | //! implementation detail and can be ignored. However, for high throughput 14 | //! clients, this should be noted as performance might not be as this collection 15 | //! can not make better use of the CPU cache in comparison to array-based 16 | //! containers. 17 | 18 | mod iter; 19 | mod lfu; 20 | mod timed_lfu; 21 | 22 | pub use iter::LfuCacheIter; 23 | pub use lfu::entry::{Entry, OccupiedEntry, VacantEntry}; 24 | pub use lfu::LfuCache; 25 | pub use timed_lfu::TimedLfuCache; 26 | -------------------------------------------------------------------------------- /src/timed_lfu.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::hash::Hash; 3 | use std::iter::FusedIterator; 4 | use std::num::NonZeroUsize; 5 | use std::rc::Rc; 6 | use std::time::{Duration, Instant}; 7 | 8 | use crate::lfu::LfuCache; 9 | use crate::{iter::LfuCacheIter, Entry}; 10 | 11 | /// A LFU cache with additional eviction conditions based on the time an entry 12 | /// has been in cache. 13 | /// 14 | /// Note that the performance of this cache for all operations that evict 15 | /// expired entries is O(log n). 16 | // This is re-exported at the crate root, so this lint can be safely ignored. 17 | #[allow(clippy::module_name_repetitions)] 18 | #[derive(Debug, Eq)] 19 | pub struct TimedLfuCache { 20 | cache: LfuCache, 21 | expiration: Option, 22 | expiry_set: BTreeSet>, 23 | } 24 | 25 | impl PartialEq for TimedLfuCache { 26 | fn eq(&self, other: &Self) -> bool { 27 | // We don't need to compare the expiry_heap here since it's meaningless 28 | // to. Because we insert instants it's almost guaranteed that the 29 | // expiry_heap is never equal. As a result, it's easier (and more 30 | // more reasonable) to compare just the cache and expirations. 31 | self.cache.eq(&other.cache) && self.expiration == other.expiration 32 | } 33 | } 34 | 35 | unsafe impl Send for TimedLfuCache {} 36 | unsafe impl Sync for TimedLfuCache {} 37 | 38 | impl TimedLfuCache { 39 | /// Constructs an unbounded LFU cache with an time-to-live for its elements. 40 | /// Cache elements that have been in the cache longer than the provided 41 | /// duration are automatically evicted. 42 | #[inline] 43 | #[must_use] 44 | pub fn with_expiration(expiration: Duration) -> Self { 45 | Self::with_capacity_and_expiration(0, expiration) 46 | } 47 | 48 | /// Constructs a bounded LFU cache with an time-to-live for its elements. 49 | /// Cache elements that have been in the cache longer than the provided 50 | /// duration are automatically evicted. 51 | #[inline] 52 | #[must_use] 53 | pub fn with_capacity_and_expiration(capacity: usize, expiration: Duration) -> Self { 54 | Self { 55 | cache: LfuCache::with_capacity(capacity), 56 | expiration: Some(expiration), 57 | expiry_set: BTreeSet::new(), 58 | } 59 | } 60 | 61 | /// Sets the new capacity. If the provided capacity is zero, then this 62 | /// will turn the cache into an unbounded cache. If the new capacity is less 63 | /// than the current capacity, the least frequently used items are evicted 64 | /// until the number of items is equal to the capacity. 65 | /// 66 | /// Calling this method will automatically evict expired entries before 67 | /// inserting the value. 68 | /// 69 | /// If the cache becomes unbounded, then users must manually call 70 | /// [`Self::pop_lfu`] to remove the least frequently used item. 71 | #[inline] 72 | pub fn set_capacity(&mut self, new_capacity: usize) { 73 | self.evict_expired(); 74 | self.cache.set_capacity(new_capacity); 75 | } 76 | 77 | /// Inserts a value into the cache using the provided key. If the value 78 | /// already exists, then the value is replaced with the provided value and 79 | /// the frequency is reset. 80 | /// 81 | /// Calling this method will automatically evict expired entries before 82 | /// inserting the value. 83 | /// 84 | /// The returned Option will return an evicted value, if a value needed to 85 | /// be evicted. This includes the old value, if the previously existing 86 | /// value was present under the same key. 87 | pub fn insert(&mut self, key: Key, value: Value) -> Option { 88 | self.evict_expired(); 89 | let key_rc = Rc::new(key); 90 | self.expiry_set 91 | .insert(ExpirationSetEntry(Rc::clone(&key_rc), Instant::now())); 92 | self.cache.insert_rc(key_rc, value) 93 | } 94 | 95 | /// Gets a value and incrementing the internal frequency counter of that 96 | /// value, if it exists. 97 | /// 98 | /// Calling this method will automatically evict expired entries before 99 | /// looking up the value. 100 | pub fn get(&mut self, key: &Key) -> Option<&Value> { 101 | self.evict_expired(); 102 | let (key, value) = self.cache.get_rc_key_value(key)?; 103 | let entry = ExpirationSetEntry(key, Instant::now()); 104 | // Since ExpirationSetEntry's equality is based on the key itself, 105 | // replace will remove the old entry (and thus old time). 106 | self.expiry_set.replace(entry); 107 | Some(value) 108 | } 109 | 110 | /// Gets a mutable value and incrementing the internal frequency counter of 111 | /// that value, if it exists. 112 | /// 113 | /// Calling this method will automatically evict expired entries before 114 | /// looking up the value. 115 | pub fn get_mut(&mut self, key: &Key) -> Option<&mut Value> { 116 | self.evict_expired(); 117 | let (key, value) = self.cache.get_rc_key_value_mut(key)?; 118 | let entry = ExpirationSetEntry(key, Instant::now()); 119 | // Since ExpirationSetEntry's equality is based on the key itself, 120 | // replace will remove the old entry (and thus old time). 121 | self.expiry_set.replace(entry); 122 | Some(value) 123 | } 124 | 125 | /// Removes a value from the cache by key, if it exists. 126 | /// 127 | /// Calling this method will automatically evict expired entries before 128 | /// removing the value. 129 | #[inline] 130 | pub fn remove(&mut self, key: &Key) -> Option { 131 | self.evict_expired(); 132 | self.cache.remove(key) 133 | } 134 | 135 | /// Gets the given key's corresponding entry in the LFU cache for in-place 136 | /// manipulation. If the key refers to an occupied entry, that entry then is 137 | /// immediately considered accessed, even if no reading or writing is 138 | /// performed. This behavior is a limitation of the Entry API. 139 | /// 140 | /// Calling this method method will automatically evict expired entries 141 | /// before returning the entry struct. 142 | #[inline] 143 | pub fn entry(&mut self, key: Key) -> Entry<'_, Key, Value> { 144 | self.evict_expired(); 145 | self.cache.entry(key) 146 | } 147 | 148 | /// Evicts the least frequently used value and returns it. If the cache is 149 | /// empty, then this returns None. If there are multiple items that have an 150 | /// equal access count, then the most recently added value is evicted. 151 | /// 152 | /// Calling this method will automatically evict expired entries before 153 | /// returning the LFU item. 154 | #[inline] 155 | pub fn pop_lfu(&mut self) -> Option { 156 | self.evict_expired(); 157 | self.cache.pop_lfu() 158 | } 159 | 160 | /// Evicts the least frequently used key-value pair and returns it. If the 161 | /// cache is empty, then this returns None. If there are multiple items that 162 | /// have an equal access count, then the most recently added key-value pair 163 | /// is evicted. 164 | /// 165 | /// Calling this method will automatically evict expired entries before 166 | /// returning the LFU item. 167 | #[inline] 168 | pub fn pop_lfu_key_value(&mut self) -> Option<(Key, Value)> { 169 | self.evict_expired(); 170 | self.cache.pop_lfu_key_value() 171 | } 172 | 173 | /// Evicts the least frequently used value and returns it, the key it was 174 | /// inserted under, and the frequency it had. If the cache is empty, then 175 | /// this returns None. If there are multiple items that have an equal access 176 | /// count, then the most recently added key-value pair is evicted. 177 | /// 178 | /// Calling this method will automatically evict expired entries before 179 | /// returning the LFU item. 180 | #[inline] 181 | pub fn pop_lfu_key_value_frequency(&mut self) -> Option<(Key, Value, usize)> { 182 | self.evict_expired(); 183 | self.cache.pop_lfu_key_value_frequency() 184 | } 185 | 186 | /// Clears the cache, returning the iterator of the previous cached values. 187 | #[inline] 188 | pub fn clear(&mut self) -> LfuCacheIter { 189 | self.expiry_set.clear(); 190 | self.cache.clear() 191 | } 192 | 193 | /// Peeks at the next value to be evicted, if there is one. This will not 194 | /// increment the access counter for that value, or evict expired entries. 195 | #[inline] 196 | #[must_use] 197 | pub fn peek_lfu(&self) -> Option<&Value> { 198 | self.cache.peek_lfu() 199 | } 200 | 201 | /// Peeks at the key for the next value to be evicted, if there is one. This 202 | /// will not increment the access counter for that value. 203 | #[inline] 204 | #[must_use] 205 | pub fn peek_lfu_key(&self) -> Option<&Key> { 206 | self.cache.peek_lfu_key() 207 | } 208 | 209 | /// Returns the current capacity of the cache. 210 | #[inline] 211 | #[must_use] 212 | pub const fn capacity(&self) -> Option { 213 | self.cache.capacity() 214 | } 215 | 216 | /// Returns the current number of items in the cache. This is a constant 217 | /// time operation. Calling this method does not evict expired items, so it 218 | /// is possible that this will include stale items. Consider calling 219 | /// [`Self::evict_expired`] if this is a concern. 220 | #[inline] 221 | #[must_use] 222 | pub const fn len(&self) -> usize { 223 | self.cache.len() 224 | } 225 | 226 | /// Returns if the cache contains no elements. Calling this method does not 227 | /// evict expired items, so it is possible that this will include stale 228 | /// items. Consider calling [`Self::evict_expired`] if this is a concern. 229 | #[inline] 230 | #[must_use] 231 | pub const fn is_empty(&self) -> bool { 232 | self.cache.is_empty() 233 | } 234 | 235 | /// Returns if the cache is unbounded. 236 | #[inline] 237 | #[must_use] 238 | pub const fn is_unbounded(&self) -> bool { 239 | self.cache.is_unbounded() 240 | } 241 | 242 | /// Returns the frequencies that this cache has. This is a linear time 243 | /// operation. Calling this method does not evict expired items, so it is 244 | /// possible that this will include stale items. Consider calling 245 | /// [`Self::evict_expired`] if this is a concern. 246 | #[inline] 247 | #[must_use] 248 | pub fn frequencies(&self) -> Vec { 249 | self.cache.frequencies() 250 | } 251 | 252 | /// Sets the capacity to the amount of objects currently in the cache. If 253 | /// no items were in the cache, the cache becomes unbounded. Note that this 254 | /// _will_ evict expired items before shrinking, so it is recommended to 255 | /// directly call [`Self::set_capacity`] for a more consistent size. 256 | #[inline] 257 | pub fn shrink_to_fit(&mut self) { 258 | self.evict_expired(); 259 | self.set_capacity(self.len()); 260 | } 261 | 262 | /// Returns an iterator over the keys of the LFU cache in any order. Note 263 | /// that this will not automatically evict expired items, so stale items 264 | /// may appear in the iterator. Consider calling [`Self::evict_expired`] if 265 | /// this is a concern. 266 | #[inline] 267 | pub fn keys(&self) -> impl Iterator + FusedIterator + '_ { 268 | self.cache.keys() 269 | } 270 | 271 | /// Returns an iterator over the values of the LFU cache in any order. Note 272 | /// that this does **not** increment the count for any of the values and 273 | /// will not automatically evict expired items, so stale items may appear 274 | /// in the iterator. Consider calling [`Self::evict_expired`] if this is a 275 | /// concern. 276 | #[inline] 277 | pub fn peek_values(&self) -> impl Iterator + FusedIterator + '_ { 278 | self.cache.peek_values() 279 | } 280 | 281 | /// Returns an iterator over the keys and values of the LFU cache in any 282 | /// order. Note that this does **not** increment the count for any of the 283 | /// values and will not automatically evict expired items, so stale items 284 | /// may appear in the iterator. Consider calling [`Self::evict_expired`] 285 | /// if this is a concern. 286 | #[inline] 287 | pub fn peek_iter(&self) -> impl Iterator + FusedIterator + '_ { 288 | self.cache.peek_iter() 289 | } 290 | 291 | /// Manually evict expired entires. Note that it is possible to have stale 292 | /// entries _after_ this method was called as it is possible for entries to 293 | /// have expired right after calling this function. 294 | pub fn evict_expired(&mut self) { 295 | if let Some(duration) = self.expiration { 296 | let cut_off = Instant::now() - duration; 297 | let mut break_next = false; 298 | let mut drain_key = None; 299 | for key in &self.expiry_set { 300 | if break_next { 301 | // Necessarily needs to clone here, as otherwise the borrow 302 | // on self.expiry_set lasts for drain_key's lifetime and 303 | // we need to mutably borrow later 304 | drain_key = Some(key.clone()); 305 | break; 306 | } 307 | 308 | break_next = key.1 <= cut_off; 309 | } 310 | 311 | if let Some(key) = drain_key { 312 | let mut to_evict = self.expiry_set.split_off(&key); 313 | // to_evict contains the values we want to retain, so do a quick 314 | // swap here 315 | std::mem::swap(&mut to_evict, &mut self.expiry_set); 316 | for ExpirationSetEntry(key, _) in to_evict { 317 | self.cache.remove(&key); 318 | } 319 | } else if break_next { 320 | // all entries are stale 321 | self.clear(); 322 | } 323 | } 324 | } 325 | } 326 | 327 | /// A struct whose order is dependent on instant, but whose equality is based 328 | /// on the provided key. This allows us to fetch key values by the key only, 329 | /// but allows us to have an ordered set of elements based on when it was added 330 | /// for quick eviction lookup. 331 | #[derive(Debug, Eq)] 332 | struct ExpirationSetEntry(Rc, Instant); 333 | 334 | impl PartialEq for ExpirationSetEntry { 335 | fn eq(&self, other: &Self) -> bool { 336 | self.0.eq(&other.0) 337 | } 338 | } 339 | 340 | impl Clone for ExpirationSetEntry { 341 | fn clone(&self) -> Self { 342 | Self(Rc::clone(&self.0), self.1) 343 | } 344 | } 345 | 346 | impl Ord for ExpirationSetEntry { 347 | #[inline] 348 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 349 | self.1.cmp(&other.1) 350 | } 351 | } 352 | 353 | impl PartialOrd for ExpirationSetEntry { 354 | #[inline] 355 | fn partial_cmp(&self, other: &Self) -> Option { 356 | Some(self.cmp(other)) 357 | } 358 | } 359 | 360 | impl Extend<(Key, Value)> for TimedLfuCache { 361 | /// Inserts the items from the iterator into the cache. Note that this may 362 | /// evict items if the number of elements in the iterator plus the number of 363 | /// current items in the cache exceeds the capacity of the cache. 364 | fn extend>(&mut self, iter: T) { 365 | for (k, v) in iter { 366 | self.insert(k, v); 367 | } 368 | } 369 | } 370 | 371 | impl IntoIterator for TimedLfuCache { 372 | type Item = (Key, Value); 373 | 374 | type IntoIter = LfuCacheIter; 375 | 376 | fn into_iter(self) -> Self::IntoIter { 377 | LfuCacheIter(self.cache) 378 | } 379 | } 380 | 381 | #[cfg(test)] 382 | mod timed { 383 | use crate::TimedLfuCache; 384 | use std::time::Duration; 385 | 386 | #[test] 387 | fn old_items_are_evicted() { 388 | let duration = Duration::from_millis(250); 389 | let mut cache = TimedLfuCache::with_expiration(duration); 390 | cache.insert(1, 2); 391 | assert_eq!(cache.get(&1), Some(&2)); 392 | std::thread::sleep(duration * 2); 393 | assert!(cache.get(&1).is_none()); 394 | } 395 | 396 | #[test] 397 | fn non_expired_remains() { 398 | let duration = Duration::from_millis(250); 399 | let mut cache = TimedLfuCache::with_expiration(duration); 400 | cache.insert(1, 2); 401 | assert_eq!(cache.get(&1), Some(&2)); 402 | std::thread::sleep(duration / 2); 403 | cache.insert(3, 4); 404 | assert_eq!(cache.get(&1), Some(&2)); 405 | assert_eq!(cache.get(&3), Some(&4)); 406 | std::thread::sleep(duration + duration / 4); 407 | assert!(cache.get(&1).is_none()); 408 | assert_eq!(cache.get(&3), Some(&4)); 409 | } 410 | } 411 | --------------------------------------------------------------------------------