"deploy/vscode:/vscode.git/clone" did not exist on "e43b30506cb7221ab678b45814cab7f0cd5802f2"
evictor.rs 4.81 KB
Newer Older
1
// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
3
// SPDX-License-Identifier: Apache-2.0

4
5
use std::cmp::{Eq, Ordering};
use std::collections::{BTreeSet, HashMap};
6
use std::hash::Hash;
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

/// A wrapper for (T, counter) that implements Ord based only on counter
#[derive(Debug, Clone, Eq, PartialEq)]
struct PriorityItem<T> {
    item: T,
    counter: i64,
}

impl<T: Eq> Ord for PriorityItem<T> {
    fn cmp(&self, other: &Self) -> Ordering {
        self.counter.cmp(&other.counter)
    }
}

impl<T: Eq> PartialOrd for PriorityItem<T> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
26
27

/// An LRU evictor that maintains objects and evicts them based on their
28
/// priority counter. Lower counter values are evicted first.
29
30
#[derive(Debug)]
pub struct LRUEvictor<T: Clone + Eq + Hash> {
31
32
33
34
    free_table: HashMap<T, i64>,
    priority_queue: BTreeSet<PriorityItem<T>>,
    positive_counter: i64,
    negative_counter: i64,
35
36
37
38
39
40
}

impl<T: Clone + Eq + Hash> Default for LRUEvictor<T> {
    fn default() -> Self {
        Self {
            free_table: HashMap::new(),
41
42
43
            priority_queue: BTreeSet::new(),
            positive_counter: 0,
            negative_counter: 0,
44
45
46
47
48
        }
    }
}

impl<T: Clone + Eq + Hash> LRUEvictor<T> {
49
50
    pub fn new(_cleanup_threshold: usize) -> Self {
        Self::default()
51
52
    }

53
54
    pub fn keys(&self) -> std::collections::hash_map::Keys<'_, T, i64> {
        self.free_table.keys()
55
56
    }

57
58
59
60
61
62
    fn update(&mut self, object: T, counter: i64) {
        self.free_table.insert(object.clone(), counter);
        self.priority_queue.insert(PriorityItem {
            item: object,
            counter,
        });
63
64
65
    }

    pub fn insert(&mut self, object: T) {
66
67
68
69
70
71
72
        // Remove old entry if it exists
        if let Some(&old_counter) = self.free_table.get(&object) {
            self.priority_queue.remove(&PriorityItem {
                item: object.clone(),
                counter: old_counter,
            });
        }
73

74
75
76
77
78
        // Increment positive counter and insert
        self.positive_counter += 1;
        let counter = self.positive_counter;

        self.update(object, counter);
79
80
    }

81
82
83
84
85
86
87
88
    /// Push an object to the front with negative counter (highest priority for eviction)
    pub fn push_front(&mut self, object: T) {
        // Remove old entry if it exists
        if let Some(&old_counter) = self.free_table.get(&object) {
            self.priority_queue.remove(&PriorityItem {
                item: object.clone(),
                counter: old_counter,
            });
89
90
        }

91
92
93
        // Decrement negative counter and insert
        self.negative_counter -= 1;
        let counter = self.negative_counter;
94

95
96
        self.update(object, counter);
    }
97

98
99
    pub fn contains(&self, object: &T) -> bool {
        self.free_table.contains_key(object)
100
101
    }

102
103
104
105
106
107
108
    /// Evict an object based on LRU policy (lowest counter value)
    /// Returns the evicted object or None if no objects are available
    pub fn evict(&mut self) -> Option<T> {
        self.priority_queue.pop_first().map(|item| {
            self.free_table.remove(&item.item);
            item.item
        })
109
110
111
    }

    pub fn remove(&mut self, object: &T) -> bool {
112
113
114
115
116
117
118
119
120
121
        let Some(&counter) = self.free_table.get(object) else {
            return false;
        };

        self.free_table.remove(object);
        self.priority_queue.remove(&PriorityItem {
            item: object.clone(),
            counter,
        });
        true
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    }

    pub fn len(&self) -> usize {
        self.free_table.len()
    }

    pub fn is_empty(&self) -> bool {
        self.free_table.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

137
138
139
140
    #[test]
    fn test_lru_evictor_eviction_order() {
        // Create a new LRUEvictor
        let mut evictor = LRUEvictor::<i32>::new(1); // threshold value doesn't matter anymore
141

142
        // Add items in the specified order
143
144
145
146
147
        evictor.insert(4);
        evictor.insert(3);
        evictor.insert(2);
        evictor.insert(1);
        evictor.insert(5);
148
149
150
151
        evictor.insert(1); // Updates counter for 1
        evictor.insert(4); // Updates counter for 4
        evictor.insert(2); // Updates counter for 2
        evictor.push_front(4);
152
153

        // Verify the eviction order
154
155
        let evicted = evictor.evict().unwrap();
        assert_eq!(evicted, 4);
156
157
158
159
160
161
162
163
164
165
166
167
        let evicted = evictor.evict().unwrap();
        assert_eq!(evicted, 3);
        let evicted = evictor.evict().unwrap();
        assert_eq!(evicted, 5);
        let evicted = evictor.evict().unwrap();
        assert_eq!(evicted, 1);
        let evicted = evictor.evict().unwrap();
        assert_eq!(evicted, 2);
        let evicted = evictor.evict();
        assert_eq!(evicted, None);
        assert_eq!(evictor.len(), 0);
    }
168
169

    // ... existing test_push_front test ...
170
}