1use std::collections::HashMap;
34use std::hash::{Hash, Hasher};
35
36pub trait AdmissionPolicy<K> {
38 fn record_access(&mut self, key: &K);
40
41 fn should_admit(&self, new_key: &K, victim_key: &K) -> bool;
45
46 fn reset(&mut self);
48}
49
50pub struct TinyLFU {
56 sketch: CountMinSketch,
58 doorkeeper: DoorKeeper,
60 sample_size: usize,
62 samples: usize,
64}
65
66impl TinyLFU {
67 #[must_use]
73 #[inline]
74 pub fn new(capacity: usize, hash_functions: usize) -> Self {
75 Self {
76 sketch: CountMinSketch::new(capacity, hash_functions),
77 doorkeeper: DoorKeeper::new(capacity),
78 sample_size: capacity * 10,
79 samples: 0,
80 }
81 }
82
83 #[must_use]
85 #[inline]
86 pub fn estimate_frequency<K: Hash>(&self, key: &K) -> u32 {
87 self.sketch.estimate(key)
88 }
89}
90
91impl<K: Hash> AdmissionPolicy<K> for TinyLFU {
92 fn record_access(&mut self, key: &K) {
93 self.doorkeeper.insert(key);
95
96 self.sketch.increment(key);
98
99 self.samples += 1;
101 if self.samples >= self.sample_size {
102 self.sketch.halve();
103 self.samples = 0;
104 }
105 }
106
107 fn should_admit(&self, new_key: &K, victim_key: &K) -> bool {
108 let new_in_doorkeeper = self.doorkeeper.might_contain(new_key);
109 let victim_in_doorkeeper = self.doorkeeper.might_contain(victim_key);
110
111 if new_in_doorkeeper && !victim_in_doorkeeper {
113 return true;
114 }
115
116 if !new_in_doorkeeper && victim_in_doorkeeper {
118 return false;
119 }
120
121 let new_freq = self.sketch.estimate(new_key);
123 let victim_freq = self.sketch.estimate(victim_key);
124
125 new_freq > victim_freq
126 }
127
128 fn reset(&mut self) {
129 self.sketch.reset();
130 self.doorkeeper.reset();
131 self.samples = 0;
132 }
133}
134
135struct CountMinSketch {
137 table: Vec<Vec<u32>>,
139 depth: usize,
141 width: usize,
143}
144
145impl CountMinSketch {
146 fn new(capacity: usize, depth: usize) -> Self {
148 let width = capacity.next_power_of_two();
149 let table = vec![vec![0; width]; depth];
150
151 Self {
152 table,
153 depth,
154 width,
155 }
156 }
157
158 fn increment<K: Hash>(&mut self, key: &K) {
160 for i in 0..self.depth {
161 let hash = self.hash(key, i);
162 let index = (hash as usize) % self.width;
163 self.table[i][index] = self.table[i][index].saturating_add(1);
164 }
165 }
166
167 fn estimate<K: Hash>(&self, key: &K) -> u32 {
169 (0..self.depth)
170 .map(|i| {
171 let hash = self.hash(key, i);
172 let index = (hash as usize) % self.width;
173 self.table[i][index]
174 })
175 .min()
176 .unwrap_or(0)
177 }
178
179 fn halve(&mut self) {
181 for row in &mut self.table {
182 for count in row {
183 *count /= 2;
184 }
185 }
186 }
187
188 fn reset(&mut self) {
190 for row in &mut self.table {
191 row.fill(0);
192 }
193 }
194
195 fn hash<K: Hash>(&self, key: &K, seed: usize) -> u64 {
197 let mut hasher = std::collections::hash_map::DefaultHasher::new();
198 key.hash(&mut hasher);
199 seed.hash(&mut hasher);
200 hasher.finish()
201 }
202}
203
204struct DoorKeeper {
206 bits: Vec<bool>,
207 size: usize,
208}
209
210impl DoorKeeper {
211 fn new(capacity: usize) -> Self {
212 let size = capacity.next_power_of_two();
213 Self {
214 bits: vec![false; size],
215 size,
216 }
217 }
218
219 fn insert<K: Hash>(&mut self, key: &K) {
220 let hash = self.hash(key);
221 let index = (hash as usize) % self.size;
222 self.bits[index] = true;
223 }
224
225 fn might_contain<K: Hash>(&self, key: &K) -> bool {
226 let hash = self.hash(key);
227 let index = (hash as usize) % self.size;
228 self.bits[index]
229 }
230
231 fn reset(&mut self) {
232 self.bits.fill(false);
233 }
234
235 fn hash<K: Hash>(&self, key: &K) -> u64 {
236 let mut hasher = std::collections::hash_map::DefaultHasher::new();
237 key.hash(&mut hasher);
238 hasher.finish()
239 }
240}
241
242pub struct SLRU<K: Eq + Hash + Clone> {
248 probationary: HashMap<K, u64>,
250 protected: HashMap<K, u64>,
252 #[allow(dead_code)]
254 protected_ratio: f64,
255 counter: u64,
257}
258
259impl<K: Eq + Hash + Clone> SLRU<K> {
260 #[must_use]
265 #[inline]
266 pub fn new(protected_ratio: f64) -> Self {
267 Self {
268 probationary: HashMap::new(),
269 protected: HashMap::new(),
270 protected_ratio: protected_ratio.clamp(0.0, 1.0),
271 counter: 0,
272 }
273 }
274
275 #[must_use]
277 #[inline]
278 pub fn is_protected(&self, key: &K) -> bool {
279 self.protected.contains_key(key)
280 }
281
282 #[must_use]
284 #[inline]
285 pub fn get_segment(&self, key: &K) -> Option<Segment> {
286 if self.protected.contains_key(key) {
287 Some(Segment::Protected)
288 } else if self.probationary.contains_key(key) {
289 Some(Segment::Probationary)
290 } else {
291 None
292 }
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum Segment {
299 Probationary,
301 Protected,
303}
304
305impl<K: Eq + Hash + Clone> AdmissionPolicy<K> for SLRU<K> {
306 fn record_access(&mut self, key: &K) {
307 self.counter += 1;
308
309 if self.probationary.contains_key(key) {
311 self.probationary.remove(key);
313 self.protected.insert(key.clone(), self.counter);
314 } else if self.protected.contains_key(key) {
315 self.protected.insert(key.clone(), self.counter);
317 } else {
318 self.probationary.insert(key.clone(), self.counter);
320 }
321 }
322
323 fn should_admit(&self, new_key: &K, victim_key: &K) -> bool {
324 match (self.get_segment(new_key), self.get_segment(victim_key)) {
325 (_, Some(Segment::Probationary)) => true, (Some(Segment::Protected), Some(Segment::Protected)) => {
327 let new_time = self.protected.get(new_key).copied().unwrap_or(0);
329 let victim_time = self.protected.get(victim_key).copied().unwrap_or(0);
330 new_time > victim_time
331 }
332 (Some(Segment::Probationary), Some(Segment::Protected)) => false,
333 _ => true, }
335 }
336
337 fn reset(&mut self) {
338 self.probationary.clear();
339 self.protected.clear();
340 self.counter = 0;
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_tinylfu_basic() {
350 let mut policy = TinyLFU::new(100, 4);
351
352 for _ in 0..10 {
354 policy.record_access(&"hot");
355 }
356
357 policy.record_access(&"cold");
359
360 assert!(policy.should_admit(&"hot", &"cold"));
362 assert!(!policy.should_admit(&"cold", &"hot"));
363 }
364
365 #[test]
366 fn test_tinylfu_doorkeeper() {
367 let mut policy = TinyLFU::new(100, 4);
368
369 policy.record_access(&"recent");
371 assert!(policy.should_admit(&"recent", &"victim"));
372 }
373
374 #[test]
375 fn test_tinylfu_frequency_estimation() {
376 let mut policy = TinyLFU::new(100, 4);
377
378 for _ in 0..5 {
379 policy.record_access(&"item");
380 }
381
382 let freq = policy.estimate_frequency(&"item");
383 assert!(freq >= 5);
384 }
385
386 #[test]
387 fn test_slru_promotion() {
388 let mut policy: SLRU<&str> = SLRU::new(0.8);
389
390 policy.record_access(&"item");
392 assert_eq!(policy.get_segment(&"item"), Some(Segment::Probationary));
393
394 policy.record_access(&"item");
396 assert_eq!(policy.get_segment(&"item"), Some(Segment::Protected));
397 }
398
399 #[test]
400 fn test_slru_admission() {
401 let mut policy: SLRU<&str> = SLRU::new(0.8);
402
403 policy.record_access(&"protected");
405 policy.record_access(&"protected");
406
407 policy.record_access(&"probationary");
409
410 assert!(policy.should_admit(&"new", &"probationary"));
412
413 assert!(!policy.should_admit(&"probationary", &"protected"));
415 }
416
417 #[test]
418 fn test_count_min_sketch() {
419 let mut sketch = CountMinSketch::new(100, 4);
420
421 sketch.increment(&"key1");
422 sketch.increment(&"key1");
423 sketch.increment(&"key1");
424
425 assert_eq!(sketch.estimate(&"key1"), 3);
426 assert_eq!(sketch.estimate(&"key2"), 0);
427 }
428
429 #[test]
430 fn test_count_min_sketch_halving() {
431 let mut sketch = CountMinSketch::new(100, 4);
432
433 for _ in 0..10 {
434 sketch.increment(&"key");
435 }
436
437 assert_eq!(sketch.estimate(&"key"), 10);
438
439 sketch.halve();
440 assert_eq!(sketch.estimate(&"key"), 5);
441 }
442
443 #[test]
444 fn test_doorkeeper() {
445 let mut doorkeeper = DoorKeeper::new(100);
446
447 doorkeeper.insert(&"item");
448 assert!(doorkeeper.might_contain(&"item"));
449
450 doorkeeper.reset();
451 assert!(!doorkeeper.might_contain(&"item"));
452 }
453}