clockpro_cache/
lib.rs

1//! This is an implementation of the [CLOCK-Pro cache] algorithm.
2//!
3//! CLOCK-Pro keeps track of recently referenced and recently evicted cache entries, which allows
4//! it to avoid the evictions that weak access patterns such as scan and loop typically induce in
5//! LRU and CLOCK.
6//!
7//! [CLOCK-Pro cache]: https://static.usenix.org/event/usenix05/tech/general/full_papers/jiang/jiang_html/html.html
8#[macro_use]
9extern crate bitflags;
10
11use crate::token_ring::{Token, TokenRing};
12use std::borrow::Borrow;
13use std::collections::HashMap;
14use std::hash::Hash;
15use std::mem::MaybeUninit;
16
17bitflags! {
18    struct NodeType: u8 {
19        const EMPTY     = 0b00001;
20        const HOT       = 0b00010;
21        const COLD      = 0b00100;
22        const TEST      = 0b01000;
23        const MASK      = Self::EMPTY.bits() | Self::HOT.bits() | Self::COLD.bits() | Self::TEST.bits();
24        const REFERENCE = 0b10000;
25    }
26}
27
28struct Node<K, V> {
29    key: MaybeUninit<K>,
30    value: Option<V>,
31    node_type: NodeType,
32}
33
34impl<K, V> Default for Node<K, V> {
35    fn default() -> Self {
36        Node {
37            key: MaybeUninit::uninit(),
38            value: None,
39            node_type: NodeType::EMPTY,
40        }
41    }
42}
43
44/// A CLOCK-Pro cache that maps keys to values.
45pub struct ClockProCache<K, V> {
46    capacity: usize,
47    test_capacity: usize,
48    cold_capacity: usize,
49    map: HashMap<K, Token>,
50    ring: TokenRing,
51    nodes: Vec<Node<K, V>>,
52    hand_hot: Token,
53    hand_cold: Token,
54    hand_test: Token,
55    count_hot: usize,
56    count_cold: usize,
57    count_test: usize,
58    inserted: u64,
59    evicted: u64,
60}
61
62impl<K, V> ClockProCache<K, V>
63where
64    K: Eq + Hash + Clone,
65{
66    /// Create a new cache with the given capacity.
67    pub fn new(capacity: usize) -> Result<Self, &'static str> {
68        Self::new_with_test_capacity(capacity, capacity)
69    }
70
71    /// Create a new cache with the given value and test capacities.
72    ///
73    /// The test capacity is used for tracking recently evicted entries, so that they will
74    /// be considered frequently used if they get reinserted.
75    pub fn new_with_test_capacity(
76        capacity: usize,
77        test_capacity: usize,
78    ) -> Result<Self, &'static str> {
79        if capacity < 3 {
80            return Err("Cache size cannot be less than 3 entries");
81        }
82        let mut nodes = Vec::with_capacity(capacity + test_capacity);
83        nodes.resize_with(capacity + test_capacity, Node::default);
84        let cache = ClockProCache {
85            capacity,
86            test_capacity,
87            cold_capacity: capacity,
88            map: HashMap::with_capacity(capacity + test_capacity),
89            ring: TokenRing::with_capacity(capacity + test_capacity),
90            nodes,
91            hand_hot: 0,
92            hand_cold: 0,
93            hand_test: 0,
94            count_hot: 0,
95            count_cold: 0,
96            count_test: 0,
97            inserted: 0,
98            evicted: 0,
99        };
100        Ok(cache)
101    }
102
103    /// Returns the number of cached values.
104    #[inline]
105    pub fn len(&self) -> usize {
106        self.count_cold + self.count_hot
107    }
108
109    /// Returns `true` when no values are currently cached.
110    #[inline]
111    pub fn is_empty(&self) -> bool {
112        self.len() == 0
113    }
114
115    /// Returns the number of recently inserted values.
116    #[inline]
117    pub fn recent_len(&self) -> usize {
118        self.count_cold
119    }
120
121    /// Returns the number of frequently fetched or updated values.
122    #[inline]
123    pub fn frequent_len(&self) -> usize {
124        self.count_hot
125    }
126
127    /// Returns the number of test entries.
128    #[inline]
129    pub fn test_len(&self) -> usize {
130        self.count_test
131    }
132
133    /// Returns how many values have been inserted into the cache overall.
134    #[inline]
135    pub fn inserted(&self) -> u64 {
136        self.inserted
137    }
138
139    /// Returns how many values have been evicted from the cache.
140    #[inline]
141    pub fn evicted(&self) -> u64 {
142        self.evicted
143    }
144
145    /// Get a mutable reference to the value in the cache mapped to by `key`.
146    ///
147    /// If no value exists for `key`, this returns `None`.
148    pub fn get_mut<Q: ?Sized>(&mut self, key: &Q) -> Option<&mut V>
149    where
150        K: Borrow<Q>,
151        Q: Eq + Hash,
152    {
153        let token = *self.map.get(key)?;
154        let node = &mut self.nodes[token];
155        let value = node.value.as_mut()?;
156        node.node_type.insert(NodeType::REFERENCE);
157        Some(value)
158    }
159
160    /// Get an immutable reference to the value in the cache mapped to by `key`.
161    ///
162    /// If no value exists for `key`, this returns `None`.
163    pub fn get<Q: ?Sized>(&mut self, key: &Q) -> Option<&V>
164    where
165        Q: Hash + Eq,
166        K: Borrow<Q>,
167    {
168        let token = *self.map.get(key)?;
169        let node = &mut self.nodes[token];
170        let value = &node.value.as_ref()?;
171        node.node_type.insert(NodeType::REFERENCE);
172        Some(value)
173    }
174
175    /// Returns `true` if there is a value in the cache mapped to by `key`.
176    pub fn contains_key<Q: ?Sized>(&mut self, key: &Q) -> bool
177    where
178        Q: Hash + Eq,
179        K: Borrow<Q>,
180    {
181        if let Some(&token) = self.map.get(key) {
182            self.nodes[token].value.is_some()
183        } else {
184            false
185        }
186    }
187
188    /// Map `key` to `value` in the cache, possibly evicting old entries.
189    ///
190    /// This method returns `true` when this is a new entry, and `false` if an existing entry was
191    /// updated.
192    pub fn insert(&mut self, key: K, value: V) -> bool {
193        let token = match self.map.get(&key).cloned() {
194            None => {
195                self.meta_add(key, value, NodeType::COLD);
196                self.count_cold += 1;
197                self.inserted += 1;
198                return true;
199            }
200            Some(token) => token,
201        };
202        {
203            let mentry = &mut self.nodes[token];
204            if mentry.value.is_some() {
205                mentry.value = Some(value);
206                mentry.node_type.insert(NodeType::REFERENCE);
207                return false;
208            }
209        }
210        if self.cold_capacity < self.capacity {
211            self.cold_capacity += 1;
212        }
213        self.count_test -= 1;
214        self.meta_del(token);
215        self.meta_add(key, value, NodeType::HOT);
216        self.count_hot += 1;
217        true
218    }
219
220    /// Remove the cache entry mapped to by `key`.
221    ///
222    /// This method returns the value removed from the cache. If `key` did not map to any value,
223    /// then this returns `None`.
224    pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
225    where
226        K: Borrow<Q>,
227        Q: Eq + Hash,
228    {
229        let token = *self.map.get(key)?;
230        let node = &mut self.nodes[token];
231        let value = node.value.take();
232
233        // The key is in map, so the node must be HOT or COLD
234        if node.node_type.intersects(NodeType::HOT) {
235            self.count_hot -= 1;
236        } else if node.node_type.intersects(NodeType::COLD) {
237            self.count_cold -= 1;
238        }
239
240        self.meta_del(token);
241        value
242    }
243
244    fn meta_add(&mut self, key: K, value: V, node_type: NodeType) {
245        self.evict();
246        let token = self.ring.insert_after(self.hand_hot);
247        self.nodes[token] = Node {
248            key: MaybeUninit::new(key.clone()),
249            value: Some(value),
250            node_type,
251        };
252        self.map.insert(key, token);
253        if self.hand_cold == self.hand_hot {
254            self.hand_cold = self.ring.prev_for_token(self.hand_cold);
255        }
256    }
257
258    fn evict(&mut self) {
259        while self.count_hot + self.count_cold >= self.capacity {
260            self.run_hand_cold();
261        }
262    }
263
264    fn run_hand_cold(&mut self) {
265        let mut run_hand_test = false;
266        {
267            let mentry = &mut self.nodes[self.hand_cold];
268            if mentry.node_type.intersects(NodeType::COLD) {
269                if mentry.node_type.intersects(NodeType::REFERENCE) {
270                    mentry.node_type = NodeType::HOT;
271                    self.count_cold -= 1;
272                    self.count_hot += 1;
273                } else {
274                    mentry.node_type.remove(NodeType::MASK);
275                    mentry.node_type.insert(NodeType::TEST);
276                    mentry.value = None;
277                    self.count_cold -= 1;
278                    self.count_test += 1;
279                    run_hand_test = true
280                }
281            }
282        }
283        if run_hand_test {
284            while self.count_test > self.test_capacity {
285                self.run_hand_test();
286            }
287        }
288        self.hand_cold = self.ring.next_for_token(self.hand_cold);
289        while self.count_hot > self.capacity - self.cold_capacity {
290            self.run_hand_hot();
291        }
292    }
293
294    fn run_hand_hot(&mut self) {
295        if self.hand_hot == self.hand_test {
296            self.run_hand_test();
297        }
298        {
299            let mentry = &mut self.nodes[self.hand_hot];
300            if mentry.node_type.intersects(NodeType::HOT) {
301                if mentry.node_type.intersects(NodeType::REFERENCE) {
302                    mentry.node_type.remove(NodeType::REFERENCE);
303                } else {
304                    mentry.node_type.remove(NodeType::MASK);
305                    mentry.node_type.insert(NodeType::COLD);
306                    self.count_hot -= 1;
307                    self.count_cold += 1;
308                }
309            }
310        }
311        self.hand_hot = self.ring.next_for_token(self.hand_hot);
312    }
313
314    fn run_hand_test(&mut self) {
315        if self.hand_test == self.hand_cold {
316            self.run_hand_cold();
317        }
318        if self.nodes[self.hand_test]
319            .node_type
320            .intersects(NodeType::TEST)
321        {
322            let prev = self.ring.prev_for_token(self.hand_test);
323            let hand_test = self.hand_test;
324            self.meta_del(hand_test);
325            self.hand_test = prev;
326            self.count_test -= 1;
327            if self.cold_capacity > 1 {
328                self.cold_capacity -= 1;
329            }
330        }
331        self.hand_test = self.ring.next_for_token(self.hand_test);
332    }
333
334    fn meta_del(&mut self, token: Token) {
335        {
336            let mentry = &mut self.nodes[token];
337            mentry.node_type.remove(NodeType::MASK);
338            mentry.node_type.insert(NodeType::EMPTY);
339            mentry.value = None;
340            self.map.remove(unsafe { mentry.key.assume_init_ref() });
341        }
342        if token == self.hand_hot {
343            self.hand_hot = self.ring.prev_for_token(self.hand_hot);
344        }
345        if token == self.hand_cold {
346            self.hand_cold = self.ring.prev_for_token(self.hand_cold);
347        }
348        if token == self.hand_test {
349            self.hand_test = self.ring.prev_for_token(self.hand_test);
350        }
351        self.ring.remove(token);
352        self.evicted += 1;
353    }
354}
355
356unsafe impl<K, V> Send for ClockProCache<K, V>
357where
358    K: Send,
359    V: Send,
360{
361}
362
363unsafe impl<K, V> Sync for ClockProCache<K, V>
364where
365    K: Sync,
366    V: Sync,
367{
368}
369
370mod token_ring {
371    use slabigator::Slab;
372
373    pub type Token = usize;
374    const TOKEN_THUMBSTONE: Token = !0;
375
376    pub struct Node {
377        next: Token,
378        prev: Token,
379    }
380
381    pub struct TokenRing {
382        head: Token,
383        tail: Token,
384        slab: Slab<Node>,
385    }
386
387    impl TokenRing {
388        pub fn with_capacity(capacity: usize) -> Self {
389            if capacity < 1 {
390                panic!("A ring cannot have a capacity smaller than 1");
391            }
392            let slab = Slab::with_capacity(capacity).expect("requested capacity is too large");
393            TokenRing {
394                head: TOKEN_THUMBSTONE,
395                tail: TOKEN_THUMBSTONE,
396                slab,
397            }
398        }
399
400        #[allow(dead_code)]
401        #[inline]
402        pub fn len(&self) -> usize {
403            self.slab.len()
404        }
405
406        #[inline]
407        pub fn next_for_token(&self, token: Token) -> Token {
408            let next = self.slab[token].next;
409            if next == TOKEN_THUMBSTONE {
410                assert!(self.head != TOKEN_THUMBSTONE);
411                self.head
412            } else {
413                next
414            }
415        }
416
417        #[inline]
418        pub fn prev_for_token(&self, token: Token) -> Token {
419            let prev = self.slab[token].prev;
420            if prev == TOKEN_THUMBSTONE {
421                assert!(self.tail != TOKEN_THUMBSTONE);
422                self.tail
423            } else {
424                prev
425            }
426        }
427
428        pub fn remove(&mut self, token: Token) {
429            let (prev, next) = (self.slab[token].prev, self.slab[token].next);
430            if prev != TOKEN_THUMBSTONE {
431                self.slab[prev].next = next;
432            } else {
433                self.head = next;
434            }
435            if next != TOKEN_THUMBSTONE {
436                self.slab[next].prev = prev;
437            } else {
438                self.tail = prev;
439            }
440            self.slab[token].prev = TOKEN_THUMBSTONE;
441            self.slab[token].next = TOKEN_THUMBSTONE;
442            self.slab.remove(token).expect("removed token not in slab");
443        }
444
445        pub fn insert_after(&mut self, to: Token) -> Token {
446            if self.slab.is_empty() {
447                let node = Node {
448                    prev: TOKEN_THUMBSTONE,
449                    next: TOKEN_THUMBSTONE,
450                };
451                let token = self.slab.push_front(node).expect("over capacity");
452                self.head = token;
453                self.tail = token;
454                return token;
455            }
456            let to_prev = self.slab[to].prev;
457            let old_second = to_prev;
458            if old_second == TOKEN_THUMBSTONE {
459                let old_second = self.tail;
460                let node = Node {
461                    prev: old_second,
462                    next: TOKEN_THUMBSTONE,
463                };
464                let token = self.slab.push_front(node).expect("over capacity");
465                self.slab[old_second].next = token;
466                self.tail = token;
467                token
468            } else {
469                let node = Node {
470                    prev: old_second,
471                    next: to,
472                };
473                let token = self.slab.push_front(node).expect("over capacity");
474                self.slab[old_second].next = token;
475                self.slab[to].prev = token;
476                token
477            }
478        }
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use super::ClockProCache;
485
486    #[test]
487    fn test_cache() {
488        let mut cache = ClockProCache::new(3).unwrap();
489        cache.insert("testkey", "testvalue");
490        assert!(cache.contains_key("testkey"));
491        cache.insert("testkey2", "testvalue2");
492        assert!(cache.contains_key("testkey2"));
493        cache.insert("testkey3", "testvalue3");
494        assert!(cache.contains_key("testkey3"));
495        cache.insert("testkey4", "testvalue4");
496        assert!(cache.contains_key("testkey4"));
497        assert!(cache.contains_key("testkey3"));
498        assert!(!cache.contains_key("testkey2"));
499        cache.insert("testkey", "testvalue");
500        assert!(cache.get_mut("testkey").is_some());
501        assert!(cache.get_mut("testkey-nx").is_none());
502    }
503
504    #[test]
505    fn test_recycle() {
506        let mut cache: ClockProCache<u64, u64> = ClockProCache::new(3).unwrap();
507        for i in 0..7 {
508            assert!(cache.insert(i, i));
509        }
510        for i in 0..2 {
511            match cache.get(&i) {
512                None => {}
513                Some(x) => assert_eq!(*x, i),
514            }
515        }
516    }
517
518    #[test]
519    fn test_composite() {
520        let mut cache: ClockProCache<u64, (Vec<u8>, u64)> = ClockProCache::new(3).unwrap();
521        for i in 0..7 {
522            assert!(cache.insert(i, (vec![0u8; 12], i)));
523        }
524        for i in 0..2 {
525            match cache.get(&i) {
526                None => {}
527                Some(x) => assert_eq!(x.1, i),
528            }
529        }
530    }
531
532    #[test]
533    fn test_remove() {
534        let mut cache: ClockProCache<u64, u64> = ClockProCache::new(4).unwrap();
535        for i in 0..4 {
536            assert!(cache.insert(i, i));
537        }
538
539        assert_eq!(cache.remove(&2), Some(2));
540        assert_eq!(cache.remove(&3), Some(3));
541        assert_eq!(cache.remove(&3), None);
542
543        for i in 0..4 {
544            match i {
545                2 | 3 => assert_eq!(cache.get(&i), None),
546                _ => assert_eq!(*cache.get(&i).unwrap(), i),
547            };
548        }
549
550        // Reinsert removed entries
551        for i in 2..4 {
552            assert!(cache.insert(i, i));
553        }
554
555        // Check that all entries still exist
556        for i in 0..4 {
557            assert_eq!(*cache.get(&i).unwrap(), i);
558        }
559    }
560
561    #[test]
562    fn test_length_and_counters() {
563        let mut cache: ClockProCache<usize, usize> = ClockProCache::new(5).unwrap();
564
565        // Cache starts out empty.
566        assert_eq!(cache.is_empty(), true);
567
568        for i in 1..=5 {
569            // Cache length should increase with each new item.
570            assert!(cache.insert(i, i));
571            assert_eq!(cache.len(), i);
572        }
573
574        // Cache is no longer empty.
575        assert_eq!(cache.is_empty(), false);
576        assert_eq!(cache.inserted(), 5);
577        assert_eq!(cache.frequent_len(), 0);
578        assert_eq!(cache.recent_len(), 5);
579
580        // Cache length should be capped at capacity.
581        assert!(cache.insert(6, 6));
582        assert!(cache.insert(7, 7));
583
584        assert_eq!(cache.len(), 5);
585        assert_eq!(cache.inserted(), 7);
586        assert_eq!(cache.frequent_len(), 0);
587        assert_eq!(cache.recent_len(), 5);
588
589        // Reference the two recent values and insert new ones to run the hand
590        // and make the REFERENCED nodes HOT.
591        assert_eq!(cache.get(&6), Some(&6));
592        assert_eq!(cache.get(&7), Some(&7));
593
594        for i in 8..=15 {
595            assert!(cache.insert(i, i));
596        }
597
598        // Both 6 and 7 should be HOT and not have been evicted.
599        assert_eq!(cache.get(&6), Some(&6));
600        assert_eq!(cache.get(&7), Some(&7));
601
602        assert_eq!(cache.len(), 5);
603        assert_eq!(cache.inserted(), 15);
604        assert_eq!(cache.frequent_len(), 2);
605        assert_eq!(cache.recent_len(), 3);
606        assert_eq!(cache.test_len(), 5);
607
608        // Removing 6 and 15 should decrement HOT and COLD counters.
609        assert_eq!(cache.remove(&6), Some(6));
610        assert_eq!(cache.remove(&15), Some(15));
611        assert_eq!(cache.frequent_len(), 1);
612        assert_eq!(cache.recent_len(), 2);
613    }
614
615    #[test]
616    fn test_evicted_to_hot() {
617        let mut cache: ClockProCache<usize, usize> =
618            ClockProCache::new_with_test_capacity(3, 30).unwrap();
619
620        // Insert test capacity items.
621        for i in 0..30 {
622            assert!(cache.insert(i, i));
623        }
624
625        assert_eq!(cache.frequent_len(), 0);
626        assert_eq!(cache.recent_len(), 3);
627        assert_eq!(cache.test_len(), 27);
628
629        // 10 should be evicted but still have a TEST node.
630        assert_eq!(cache.get(&10), None);
631
632        // Inserting 0 again should replace the TEST node w/ a HOT one.
633        assert!(cache.insert(10, 10));
634        assert_eq!(cache.frequent_len(), 1);
635        assert_eq!(cache.recent_len(), 2);
636        assert_eq!(cache.test_len(), 27);
637    }
638}