memory_lru/
lib.rs

1// Copyright (c) 2015-2021 Parity Technologies
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
21//! A memory-based LRU cache.
22
23use lru::LruCache;
24
25use std::hash::Hash;
26use std::num::NonZeroUsize;
27
28const INITIAL_CAPACITY: Option<NonZeroUsize> = NonZeroUsize::new(4);
29
30/// An indicator of the resident in memory of a value.
31pub trait ResidentSize {
32    /// Return the resident size of the value. Users of the trait will depend
33    /// on this value to remain stable unless the value is mutated.
34    fn resident_size(&self) -> usize;
35}
36
37/// An LRU-cache which operates on memory used.
38pub struct MemoryLruCache<K, V> {
39    inner: LruCache<K, V>,
40    cur_size: usize,
41    max_size: usize,
42}
43
44impl<K: Eq + Hash, V: ResidentSize> MemoryLruCache<K, V> {
45    /// Create a new cache with a maximum cumulative size of values.
46    pub fn new(max_size: usize) -> Self {
47        MemoryLruCache {
48            inner: LruCache::new(INITIAL_CAPACITY.expect("4 != 0; qed")),
49            max_size: max_size,
50            cur_size: 0,
51        }
52    }
53
54    /// Insert an item.
55    pub fn insert(&mut self, key: K, val: V) {
56        let cap = self.inner.cap().get();
57
58        // grow the cache as necessary; it operates on amount of items
59        // but we're working based on memory usage.
60        if self.inner.len() == cap {
61            let next_cap = NonZeroUsize::new(cap.saturating_mul(2)).expect(
62                "only returns None if value is zero; cap is guaranteed to be non-zero; qed",
63            );
64            self.inner.resize(next_cap);
65        }
66
67        self.cur_size += val.resident_size();
68
69        // account for any element displaced from the cache.
70        if let Some(lru) = self.inner.put(key, val) {
71            self.cur_size -= lru.resident_size();
72        }
73
74        self.readjust_down();
75    }
76
77    /// Get a reference to an item in the cache. It is a logic error for its
78    /// heap size to be altered while borrowed.
79    pub fn get(&mut self, key: &K) -> Option<&V> {
80        self.inner.get(key)
81    }
82
83    /// Execute a closure with the value under the provided key.
84    pub fn with_mut<U>(&mut self, key: &K, with: impl FnOnce(Option<&mut V>) -> U) -> U {
85        let mut val = self.inner.get_mut(key);
86        let prev_size = val.as_ref().map_or(0, |v| v.resident_size());
87
88        let res = with(val.as_mut().map(|v: &mut &mut V| &mut **v));
89
90        let new_size = val.as_ref().map_or(0, |v| v.resident_size());
91
92        self.cur_size -= prev_size;
93        self.cur_size += new_size;
94
95        self.readjust_down();
96
97        res
98    }
99
100    /// Currently-used size of values in bytes.
101    pub fn current_size(&self) -> usize {
102        self.cur_size
103    }
104
105    /// Returns the number of key-value pairs that are currently in the cache.
106    pub fn len(&self) -> usize {
107        self.inner.len()
108    }
109
110    /// Returns a bool indicating whether the given key is in the cache.
111    /// Does not update the LRU list.
112    pub fn contains(&self, key: &K) -> bool {
113        self.inner.contains(key)
114    }
115
116    /// Returns a bool indicating whether the cache is empty or not.
117    pub fn is_empty(&self) -> bool {
118        self.inner.is_empty()
119    }
120
121    /// Returns a reference to the value corresponding to the key in the cache or
122    /// None if it is not present in the cache. Unlike get, peek does not update the
123    /// LRU list so the key's position will be unchanged.
124    pub fn peek(&self, key: &K) -> Option<&V> {
125        self.inner.peek(key)
126    }
127
128    fn readjust_down(&mut self) {
129        // remove elements until we are below the memory target.
130        while self.cur_size > self.max_size {
131            match self.inner.pop_lru() {
132                Some((_, v)) => self.cur_size -= v.resident_size(),
133                _ => break,
134            }
135        }
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    impl ResidentSize for Vec<u8> {
144        fn resident_size(&self) -> usize {
145            self.len()
146        }
147    }
148
149    #[test]
150    fn it_works() {
151        let mut cache = MemoryLruCache::new(256);
152        let val1 = vec![0u8; 100];
153        let size1 = val1.resident_size();
154        assert_eq!(cache.len(), 0);
155        cache.insert("hello", val1);
156
157        assert_eq!(cache.current_size(), size1);
158
159        let val2 = vec![0u8; 210];
160        let size2 = val2.resident_size();
161        cache.insert("world", val2);
162
163        assert!(cache.get(&"hello").is_none());
164        assert!(cache.get(&"world").is_some());
165
166        assert_eq!(cache.current_size(), size2);
167        assert_eq!(cache.len(), 1);
168    }
169
170    #[test]
171    fn it_works_if_cur_size_equals_max_size() {
172        let mut cache = MemoryLruCache::new(8);
173        cache.insert(1, vec![0u8, 1u8]);
174        cache.insert(2, vec![2u8, 3u8]);
175        cache.insert(3, vec![4u8, 5u8]);
176        cache.insert(4, vec![6u8, 7u8]);
177        cache.insert(5, vec![8u8, 9u8]);
178
179        assert_eq!(Some(&vec![2u8, 3u8]), cache.get(&2));
180    }
181}