Skip to main content

reth_lru/
lib.rs

1//! A length-limited LRU map tailored to reth's caches.
2//!
3//! Entries live in a `Vec` of nodes that form a doubly linked list from most to least recently
4//! used. A [`HashTable`] maps keys to node indices, so hashbrown can grow or rehash it freely
5//! without invalidating the links. Nodes freed by removals are reused by later inserts, and the
6//! node `Vec` never grows past the length limit.
7//!
8//! Insertions that evict return the evicted entry, so callers that need eviction feedback don't
9//! need a second lookup.
10
11#![doc(
12    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
13    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
14    issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
15)]
16#![cfg_attr(not(test), warn(unused_crate_dependencies))]
17#![cfg_attr(docsrs, feature(doc_cfg))]
18#![no_std]
19
20extern crate alloc;
21
22use alloc::vec::Vec;
23use alloy_primitives::map::DefaultHashBuilder;
24use core::{
25    borrow::Borrow,
26    fmt,
27    hash::{BuildHasher, Hash},
28    iter::FusedIterator,
29    mem::{self, MaybeUninit},
30};
31use hashbrown::HashTable;
32
33/// Marks the end of the list and an empty free list.
34const NONE: u32 = u32::MAX;
35
36/// Hash table capacity per node slot.
37///
38/// The table holds only `u32` indices, so a sparse table costs a few bytes per entry and keeps
39/// probe sequences short and tombstone rehashes rare under eviction churn.
40const TABLE_SLACK: usize = 2;
41
42/// A hash map holding at most `max_length` entries that evicts the least recently used entry.
43///
44/// [`get`](Self::get) and the insert methods mark an entry as most recently used, the `peek`
45/// methods don't.
46pub struct LruMap<K, V, S = DefaultHashBuilder> {
47    /// Indices of the linked nodes, keyed by the hash of their key.
48    table: HashTable<u32>,
49    nodes: Vec<Node<K, V>>,
50    hasher: S,
51    max_length: u32,
52    len: u32,
53    newest: u32,
54    oldest: u32,
55    /// Head of the vacant nodes, chained through [`Node::newer`].
56    free: u32,
57}
58
59impl<K, V> LruMap<K, V> {
60    /// Creates an empty map that holds at most `max_length` entries, using the default hasher.
61    /// Does not allocate.
62    pub fn new(max_length: u32) -> Self {
63        Self::with_hasher(max_length, DefaultHashBuilder::default())
64    }
65}
66
67impl<K, V, S> LruMap<K, V, S> {
68    /// Creates an empty map that holds at most `max_length` entries, using `hasher` to hash keys.
69    /// Does not allocate.
70    pub const fn with_hasher(max_length: u32, hasher: S) -> Self {
71        Self {
72            table: HashTable::new(),
73            nodes: Vec::new(),
74            hasher,
75            max_length,
76            len: 0,
77            newest: NONE,
78            oldest: NONE,
79            free: NONE,
80        }
81    }
82
83    /// Returns the maximum number of entries.
84    pub const fn max_length(&self) -> u32 {
85        self.max_length
86    }
87
88    /// Returns the number of entries.
89    pub const fn len(&self) -> usize {
90        self.len as usize
91    }
92
93    /// Returns `true` if the map holds no entries.
94    pub const fn is_empty(&self) -> bool {
95        self.len == 0
96    }
97
98    /// Returns the number of heap bytes allocated by the map, excluding heap memory owned by keys
99    /// and values.
100    pub fn memory_usage(&self) -> usize {
101        self.table.allocation_size() + self.nodes.capacity() * size_of::<Node<K, V>>()
102    }
103
104    /// Returns the least recently used entry without changing its position.
105    pub fn peek_oldest(&self) -> Option<(&K, &V)> {
106        (self.oldest != NONE).then(|| self.entry(self.oldest))
107    }
108
109    /// Returns the most recently used entry without changing its position.
110    pub fn peek_newest(&self) -> Option<(&K, &V)> {
111        (self.newest != NONE).then(|| self.entry(self.newest))
112    }
113
114    /// Iterates from the most to the least recently used entry.
115    pub const fn iter(&self) -> Iter<'_, K, V, S> {
116        Iter { map: self, next: self.newest, remaining: self.len() }
117    }
118
119    /// Removes all entries, keeping the allocated memory.
120    pub fn clear(&mut self) {
121        self.table.clear();
122        let newest = mem::replace(&mut self.newest, NONE);
123        self.oldest = NONE;
124        self.free = NONE;
125        self.len = 0;
126        // The map is empty before any entry is dropped, so a panicking drop only leaks the rest.
127        let mut nodes = mem::take(&mut self.nodes);
128        drop_linked(&mut nodes, newest);
129        nodes.clear();
130        self.nodes = nodes;
131    }
132
133    #[inline]
134    fn node(&self, index: u32) -> &Node<K, V> {
135        debug_assert!((index as usize) < self.nodes.len());
136        // SAFETY: indices stored in the table, the list and the free list are always in bounds.
137        unsafe { self.nodes.get_unchecked(index as usize) }
138    }
139
140    #[inline]
141    fn node_mut(&mut self, index: u32) -> &mut Node<K, V> {
142        debug_assert!((index as usize) < self.nodes.len());
143        // SAFETY: see `node`.
144        unsafe { self.nodes.get_unchecked_mut(index as usize) }
145    }
146
147    /// Returns the entry of the linked node at `index`.
148    #[inline]
149    fn entry(&self, index: u32) -> (&K, &V) {
150        let node = self.node(index);
151        // SAFETY: linked nodes are initialized.
152        unsafe { (node.key.assume_init_ref(), node.value.assume_init_ref()) }
153    }
154
155    #[inline]
156    fn unlink(&mut self, index: u32) {
157        let Node { older, newer, .. } = *self.node(index);
158        if older == NONE {
159            self.oldest = newer
160        } else {
161            self.node_mut(older).newer = newer
162        }
163        if newer == NONE {
164            self.newest = older
165        } else {
166            self.node_mut(newer).older = older
167        }
168    }
169
170    #[inline]
171    fn link_newest(&mut self, index: u32) {
172        let prev = self.newest;
173        let node = self.node_mut(index);
174        node.older = prev;
175        node.newer = NONE;
176        if prev == NONE {
177            self.oldest = index
178        } else {
179            self.node_mut(prev).newer = index
180        }
181        self.newest = index;
182    }
183
184    #[inline]
185    fn promote(&mut self, index: u32) {
186        if self.newest != index {
187            self.unlink(index);
188            self.link_newest(index);
189        }
190    }
191}
192
193impl<K, V, S> LruMap<K, V, S>
194where
195    K: Hash + Eq,
196    S: BuildHasher,
197{
198    /// Returns the value for `key` and marks it as the most recently used.
199    #[inline]
200    pub fn get<Q>(&mut self, key: &Q) -> Option<&mut V>
201    where
202        K: Borrow<Q>,
203        Q: Hash + Eq + ?Sized,
204    {
205        let index = self.find(key)?;
206        self.promote(index);
207        // SAFETY: `find` only returns linked nodes, which are initialized.
208        Some(unsafe { self.node_mut(index).value.assume_init_mut() })
209    }
210
211    /// Returns the value for `key` without changing its position.
212    #[inline]
213    pub fn peek<Q>(&self, key: &Q) -> Option<&V>
214    where
215        K: Borrow<Q>,
216        Q: Hash + Eq + ?Sized,
217    {
218        self.find(key).map(|index| self.entry(index).1)
219    }
220
221    /// Returns a mutable reference to the value for `key` without changing its position.
222    #[inline]
223    pub fn peek_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
224    where
225        K: Borrow<Q>,
226        Q: Hash + Eq + ?Sized,
227    {
228        let index = self.find(key)?;
229        // SAFETY: `find` only returns linked nodes, which are initialized.
230        Some(unsafe { self.node_mut(index).value.assume_init_mut() })
231    }
232
233    /// Returns `true` if the map contains `key`, without changing its position.
234    #[inline]
235    pub fn contains<Q>(&self, key: &Q) -> bool
236    where
237        K: Borrow<Q>,
238        Q: Hash + Eq + ?Sized,
239    {
240        self.find(key).is_some()
241    }
242
243    /// Inserts `value` for `key`, or replaces the current value, and marks `key` as the most
244    /// recently used.
245    ///
246    /// Returns the least recently used entry if it was evicted to make room. If `max_length` is
247    /// zero, nothing is stored and the given entry is returned instead.
248    #[inline]
249    pub fn insert(&mut self, key: K, value: V) -> Option<(K, V)> {
250        let hash = self.hasher.hash_one(&key);
251        if let Some(index) = self.find_hashed(hash, &key) {
252            self.promote(index);
253            // SAFETY: `find_hashed` only returns linked nodes, which are initialized.
254            *unsafe { self.node_mut(index).value.assume_init_mut() } = value;
255            return None
256        }
257        self.insert_new(hash, key, value).1
258    }
259
260    /// Returns the value for `key` and marks it as the most recently used, first inserting the
261    /// value returned by `f` if `key` is absent.
262    ///
263    /// Returns `None` only if `max_length` is zero.
264    #[inline]
265    pub fn get_or_insert(&mut self, key: K, f: impl FnOnce() -> V) -> Option<&mut V> {
266        let hash = self.hasher.hash_one(&key);
267        let index = if let Some(index) = self.find_hashed(hash, &key) {
268            self.promote(index);
269            index
270        } else {
271            let index = self.insert_new(hash, key, f()).0;
272            if index == NONE {
273                return None
274            }
275            index
276        };
277        // SAFETY: both branches yield a linked node, which is initialized.
278        Some(unsafe { self.node_mut(index).value.assume_init_mut() })
279    }
280
281    /// Removes `key` and returns its value.
282    #[inline]
283    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
284    where
285        K: Borrow<Q>,
286        Q: Hash + Eq + ?Sized,
287    {
288        let index = self.find(key)?;
289        Some(self.take(index).1)
290    }
291
292    /// Removes and returns the least recently used entry.
293    #[inline]
294    pub fn pop_oldest(&mut self) -> Option<(K, V)> {
295        (self.oldest != NONE).then(|| self.take(self.oldest))
296    }
297
298    #[inline]
299    fn find<Q>(&self, key: &Q) -> Option<u32>
300    where
301        K: Borrow<Q>,
302        Q: Hash + Eq + ?Sized,
303    {
304        self.find_hashed(self.hasher.hash_one(key), key)
305    }
306
307    #[inline]
308    fn find_hashed<Q>(&self, hash: u64, key: &Q) -> Option<u32>
309    where
310        K: Borrow<Q>,
311        Q: Hash + Eq + ?Sized,
312    {
313        let nodes = &self.nodes;
314        self.table
315            .find(hash, |&index| {
316                // SAFETY: the table only holds indices of linked nodes, which are initialized.
317                let node = unsafe { nodes.get_unchecked(index as usize) };
318                unsafe { node.key.assume_init_ref() }.borrow() == key
319            })
320            .copied()
321    }
322
323    /// Removes the linked node at `index` from the table, list and length, moves its entry out
324    /// and puts the node on the free list.
325    fn take(&mut self, index: u32) -> (K, V) {
326        // Hashing may panic, so it happens before any state changes.
327        // SAFETY: the node is linked, so its key is initialized.
328        let hash = self.hasher.hash_one(unsafe { self.node(index).key.assume_init_ref() });
329        match self.table.find_entry(hash, |&i| i == index) {
330            Ok(slot) => {
331                slot.remove();
332            }
333            // Only reachable with a `Hash` impl that is not deterministic. The table must never
334            // point at a vacant node, so fall back to a full scan.
335            Err(_) => self.table.retain(|&mut i| i != index),
336        }
337        self.unlink(index);
338        let free = self.free;
339        let node = self.node_mut(index);
340        node.newer = free;
341        // SAFETY: the node was linked, so both are initialized. It is vacant from here on.
342        let entry = unsafe { (node.key.assume_init_read(), node.value.assume_init_read()) };
343        self.free = index;
344        self.len -= 1;
345        entry
346    }
347
348    /// Inserts an absent key, evicting the oldest entry if the map is full.
349    ///
350    /// Returns the index of the new node, or [`NONE`] with the given entry if `max_length` is
351    /// zero, and the evicted entry.
352    ///
353    /// Every linked node is initialized and counted in `len` whenever user code (hashing,
354    /// equality, drops) can panic. The table may then miss an index, which only causes lookup
355    /// misses, but never holds the index of a vacant node.
356    fn insert_new(&mut self, hash: u64, key: K, value: V) -> (u32, Option<(K, V)>) {
357        if self.max_length == 0 {
358            return (NONE, Some((key, value)))
359        }
360        let evicted = (self.len >= self.max_length).then(|| self.take(self.oldest));
361        let index = if self.free == NONE {
362            if self.nodes.len() == self.nodes.capacity() {
363                self.grow();
364            }
365            self.nodes.push(Node {
366                key: MaybeUninit::new(key),
367                value: MaybeUninit::new(value),
368                older: NONE,
369                newer: NONE,
370            });
371            self.nodes.len() as u32 - 1
372        } else {
373            let index = self.free;
374            let node = self.node_mut(index);
375            let next = node.newer;
376            node.key.write(key);
377            node.value.write(value);
378            self.free = next;
379            index
380        };
381        self.link_newest(index);
382        self.len += 1;
383        let (nodes, hasher) = (&self.nodes, &self.hasher);
384        self.table.insert_unique(hash, index, |&i| hash_node(hasher, nodes, i));
385        (index, evicted)
386    }
387
388    /// Grows the node `Vec` geometrically up to `max_length` and keeps the table sparse.
389    #[cold]
390    fn grow(&mut self) {
391        let target =
392            (self.nodes.len() * 2).clamp(4.min(self.max_length as usize), self.max_length as usize);
393        self.nodes.reserve_exact(target - self.nodes.len());
394        let (nodes, hasher) = (&self.nodes, &self.hasher);
395        let additional = target.saturating_mul(TABLE_SLACK).saturating_sub(self.table.len());
396        self.table.reserve(additional, |&i| hash_node(hasher, nodes, i));
397    }
398}
399
400impl<K, V, S: Default> Default for LruMap<K, V, S> {
401    /// Returns an empty map with `max_length` zero, which stores nothing.
402    fn default() -> Self {
403        Self::with_hasher(0, S::default())
404    }
405}
406
407impl<K, V, S> Drop for LruMap<K, V, S> {
408    fn drop(&mut self) {
409        drop_linked(&mut self.nodes, self.newest);
410    }
411}
412
413impl<K, V, S> fmt::Debug for LruMap<K, V, S> {
414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415        f.debug_struct("LruMap")
416            .field("len", &self.len)
417            .field("max_length", &self.max_length)
418            .finish_non_exhaustive()
419    }
420}
421
422impl<'a, K, V, S> IntoIterator for &'a LruMap<K, V, S> {
423    type Item = (&'a K, &'a V);
424    type IntoIter = Iter<'a, K, V, S>;
425
426    fn into_iter(self) -> Self::IntoIter {
427        self.iter()
428    }
429}
430
431/// Iterator over the entries of an [`LruMap`], from most to least recently used.
432#[derive(Debug)]
433pub struct Iter<'a, K, V, S> {
434    map: &'a LruMap<K, V, S>,
435    next: u32,
436    remaining: usize,
437}
438
439impl<'a, K, V, S> Iterator for Iter<'a, K, V, S> {
440    type Item = (&'a K, &'a V);
441
442    fn next(&mut self) -> Option<Self::Item> {
443        if self.next == NONE {
444            return None
445        }
446        let map = self.map;
447        let entry = map.entry(self.next);
448        self.next = map.node(self.next).older;
449        self.remaining -= 1;
450        Some(entry)
451    }
452
453    fn size_hint(&self) -> (usize, Option<usize>) {
454        (self.remaining, Some(self.remaining))
455    }
456}
457
458impl<K, V, S> ExactSizeIterator for Iter<'_, K, V, S> {}
459
460impl<K, V, S> FusedIterator for Iter<'_, K, V, S> {}
461
462/// A list node. `key` and `value` are initialized iff the node is linked into the LRU list.
463struct Node<K, V> {
464    key: MaybeUninit<K>,
465    value: MaybeUninit<V>,
466    /// Next less recently used node, or [`NONE`].
467    older: u32,
468    /// Next more recently used node, or the next vacant node for vacant nodes, or [`NONE`].
469    newer: u32,
470}
471
472/// Drops the entries of the list starting at `newest`.
473///
474/// Callers must not treat these nodes as linked afterwards.
475fn drop_linked<K, V>(nodes: &mut [Node<K, V>], newest: u32) {
476    if !mem::needs_drop::<(K, V)>() {
477        return
478    }
479    let mut index = newest;
480    while index != NONE {
481        let node = &mut nodes[index as usize];
482        index = node.older;
483        // SAFETY: linked nodes are initialized, and the caller stops treating them as linked, so
484        // each is dropped exactly once.
485        unsafe {
486            node.key.assume_init_drop();
487            node.value.assume_init_drop();
488        }
489    }
490}
491
492/// Hashes the key of the linked node at `index`, for rehashing the table.
493#[inline]
494fn hash_node<K: Hash, V, S: BuildHasher>(hasher: &S, nodes: &[Node<K, V>], index: u32) -> u64 {
495    // SAFETY: the table only holds indices of linked nodes, which are initialized.
496    hasher.hash_one(unsafe { nodes.get_unchecked(index as usize).key.assume_init_ref() })
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502    use alloc::{
503        string::{String, ToString},
504        sync::Arc,
505        vec,
506    };
507
508    fn keys<K: Copy, V, S>(map: &LruMap<K, V, S>) -> Vec<K> {
509        map.iter().map(|(k, _)| *k).collect()
510    }
511
512    #[test]
513    fn evicts_least_recently_used() {
514        let mut map = LruMap::<u64, u64>::new(3);
515        assert_eq!(map.insert(1, 10), None);
516        assert_eq!(map.insert(2, 20), None);
517        assert_eq!(map.insert(3, 30), None);
518        assert_eq!(map.get(&1), Some(&mut 10));
519        assert_eq!(map.peek(&2), Some(&20));
520        assert_eq!(map.insert(4, 40), Some((2, 20)));
521        assert_eq!(keys(&map), [4, 1, 3]);
522        assert_eq!(map.peek_oldest(), Some((&3, &30)));
523        assert_eq!(map.peek_newest(), Some((&4, &40)));
524        assert_eq!(map.insert(3, 31), None);
525        assert_eq!(keys(&map), [3, 4, 1]);
526        assert_eq!(map.remove(&4), Some(40));
527        assert_eq!(map.get_or_insert(5, || 50), Some(&mut 50));
528        assert_eq!(map.get_or_insert(1, || 0), Some(&mut 10));
529        assert_eq!(keys(&map), [1, 5, 3]);
530        assert_eq!(map.pop_oldest(), Some((3, 31)));
531        assert_eq!(map.len(), 2);
532        assert_eq!(map.iter().len(), 2);
533    }
534
535    #[test]
536    fn zero_length_stores_nothing() {
537        let mut map = LruMap::<u64, u64>::default();
538        assert_eq!(map.insert(1, 1), Some((1, 1)));
539        assert_eq!(map.get_or_insert(1, || 1), None);
540        assert!(map.is_empty());
541        assert_eq!(map.memory_usage(), 0);
542    }
543
544    #[test]
545    fn borrowed_lookups() {
546        let mut map = LruMap::<String, u64>::new(2);
547        map.insert("a".to_string(), 1);
548        assert_eq!(map.get("a"), Some(&mut 1));
549        assert!(map.contains("a"));
550        assert_eq!(map.peek_mut("a"), Some(&mut 1));
551        assert_eq!(map.remove("a"), Some(1));
552        assert!(!map.contains("a"));
553    }
554
555    #[test]
556    fn never_grows_past_max_length() {
557        let mut map = LruMap::<u64, ()>::new(5);
558        for i in 0..100 {
559            map.insert(i, ());
560            if i % 7 == 6 {
561                map.remove(&(i - 1));
562            }
563        }
564        assert_eq!(map.nodes.capacity(), 5);
565        assert_eq!(map.len(), 5);
566    }
567
568    /// Every value is dropped exactly once across evictions, removals, replacements, clears and
569    /// drop.
570    #[test]
571    fn drops_each_value_once() {
572        let token = Arc::new(());
573        {
574            let mut map = LruMap::<u64, Arc<()>>::new(4);
575            for i in 0..64u64 {
576                map.insert(i % 9, Arc::clone(&token));
577                if i % 3 == 0 {
578                    map.remove(&(i % 7));
579                }
580                if i % 5 == 0 {
581                    map.pop_oldest();
582                }
583                map.get_or_insert(i % 11, || Arc::clone(&token));
584                assert_eq!(Arc::strong_count(&token), 1 + map.len());
585                if i == 32 {
586                    map.clear();
587                    assert_eq!(Arc::strong_count(&token), 1);
588                }
589            }
590        }
591        assert_eq!(Arc::strong_count(&token), 1);
592    }
593
594    /// Replays a random trace against a `Vec` model, newest first.
595    #[test]
596    fn matches_model() {
597        let steps = if cfg!(miri) { 2_000 } else { 100_000 };
598        for max_length in [1u32, 2, 3, 7, 64, 333] {
599            let mut map = LruMap::<u64, u64>::new(max_length);
600            let mut model: Vec<(u64, u64)> = vec![];
601            let mut x = 0x9e37_79b9_7f4a_7c15u64;
602            for step in 0..steps {
603                x ^= x << 13;
604                x ^= x >> 7;
605                x ^= x << 17;
606                let key = x % (max_length as u64 * 3 + 1);
607                let position = model.iter().position(|e| e.0 == key);
608                match x >> 61 {
609                    0..=2 => {
610                        let expected = match position {
611                            Some(p) => {
612                                model.remove(p);
613                                None
614                            }
615                            None if model.len() == max_length as usize => model.pop(),
616                            None => None,
617                        };
618                        model.insert(0, (key, step));
619                        assert_eq!(map.insert(key, step), expected);
620                    }
621                    3 | 4 => {
622                        let expected = position.map(|p| {
623                            let entry = model.remove(p);
624                            model.insert(0, entry);
625                            entry.1
626                        });
627                        assert_eq!(map.get(&key).copied(), expected);
628                    }
629                    5 => assert_eq!(map.remove(&key), position.map(|p| model.remove(p).1)),
630                    6 => assert_eq!(map.pop_oldest(), model.pop()),
631                    _ => assert_eq!(map.peek(&key).copied(), position.map(|p| model[p].1)),
632                }
633                assert_eq!(map.len(), model.len());
634                if step % 997 == 0 {
635                    assert_eq!(map.iter().map(|(k, v)| (*k, *v)).collect::<Vec<_>>(), model);
636                }
637            }
638        }
639    }
640}