subms_block_cache/features/
tinylfu.rs1use std::collections::HashMap;
32use std::hash::{Hash, Hasher};
33
34const FNV_OFFSET: u64 = 0xcbf29ce484222325;
35const FNV_PRIME: u64 = 0x100000001b3;
36
37const CMS_ROWS: usize = 4;
38const CMS_COUNTER_MAX: u32 = 15;
39
40const NIL: u32 = u32::MAX;
41
42struct Node<K, V> {
43 key: K,
44 value: V,
45 prev: u32,
46 next: u32,
47 list: Segment,
49}
50
51#[derive(Copy, Clone, PartialEq, Eq)]
52enum Segment {
53 Window,
54 Probation,
55 Protected,
56}
57
58struct Cms {
62 cols: usize,
63 rows: Vec<Vec<u8>>,
64 additions: u64,
65 sample_size: u64,
66}
67
68impl Cms {
69 fn new(cells_per_row: usize, sample_size: u64) -> Self {
70 let cols = cells_per_row.max(8).next_power_of_two();
71 Self {
72 cols,
73 rows: (0..CMS_ROWS).map(|_| vec![0u8; cols.div_ceil(2)]).collect(),
74 additions: 0,
75 sample_size,
76 }
77 }
78
79 fn idx(&self, row: usize, h: u64) -> usize {
80 let seeded = h.wrapping_mul(SEEDS[row]).wrapping_add(SEEDS[row] >> 17);
81 (seeded as usize) & (self.cols - 1)
82 }
83
84 fn read(&self, row: usize, idx: usize) -> u8 {
85 let byte = self.rows[row][idx / 2];
86 if idx & 1 == 0 {
87 byte & 0x0f
88 } else {
89 (byte >> 4) & 0x0f
90 }
91 }
92 fn write(&mut self, row: usize, idx: usize, v: u8) {
93 let i = idx / 2;
94 let v = v & 0x0f;
95 if idx & 1 == 0 {
96 self.rows[row][i] = (self.rows[row][i] & 0xf0) | v;
97 } else {
98 self.rows[row][i] = (self.rows[row][i] & 0x0f) | (v << 4);
99 }
100 }
101
102 fn increment(&mut self, hash: u64) {
103 let mut added = false;
104 for r in 0..CMS_ROWS {
105 let idx = self.idx(r, hash);
106 let cur = self.read(r, idx) as u32;
107 if cur < CMS_COUNTER_MAX {
108 self.write(r, idx, (cur + 1) as u8);
109 added = true;
110 }
111 }
112 if added {
113 self.additions += 1;
114 if self.additions >= self.sample_size {
115 self.reset();
116 }
117 }
118 }
119
120 fn estimate(&self, hash: u64) -> u32 {
121 let mut m = u32::MAX;
122 for r in 0..CMS_ROWS {
123 let idx = self.idx(r, hash);
124 let v = self.read(r, idx) as u32;
125 if v < m {
126 m = v;
127 }
128 }
129 m
130 }
131
132 fn reset(&mut self) {
134 for r in 0..CMS_ROWS {
135 for byte in self.rows[r].iter_mut() {
136 let lo = (*byte & 0x0f) >> 1;
137 let hi = ((*byte >> 4) & 0x0f) >> 1;
138 *byte = (hi << 4) | lo;
139 }
140 }
141 self.additions /= 2;
142 }
143}
144
145const SEEDS: [u64; CMS_ROWS] = [
146 0x9e3779b97f4a7c15,
147 0xbf58476d1ce4e5b9,
148 0x94d049bb133111eb,
149 0x2545f4914f6cdd1d,
150];
151
152struct Doorkeeper {
156 bits: Vec<u64>,
157 mask: u64,
158}
159
160impl Doorkeeper {
161 fn new(bits: usize) -> Self {
162 let words = bits.max(64).next_power_of_two().div_ceil(64);
163 Self {
164 bits: vec![0u64; words],
165 mask: ((words * 64) as u64) - 1,
166 }
167 }
168 fn idx(&self, hash: u64, salt: u64) -> usize {
169 ((hash.wrapping_mul(salt)) & self.mask) as usize
170 }
171 fn check_or_add(&mut self, hash: u64) -> bool {
173 let positions = [self.idx(hash, SEEDS[0]), self.idx(hash, SEEDS[1])];
174 let mut all_set = true;
175 for &p in &positions {
176 let word = p >> 6;
177 let bit = 1u64 << (p & 63);
178 if self.bits[word] & bit == 0 {
179 all_set = false;
180 self.bits[word] |= bit;
181 }
182 }
183 all_set
184 }
185 fn clear(&mut self) {
186 for w in self.bits.iter_mut() {
187 *w = 0;
188 }
189 }
190}
191
192pub struct TinyLfuCache<K, V> {
194 c: usize,
195 window_cap: usize,
196 protected_cap: usize,
197 probation_cap: usize,
198 nodes: Vec<Option<Node<K, V>>>,
199 free: Vec<u32>,
200 index: HashMap<K, u32>,
201 window_head: u32,
202 window_tail: u32,
203 window_len: usize,
204 protected_head: u32,
205 protected_tail: u32,
206 protected_len: usize,
207 probation_head: u32,
208 probation_tail: u32,
209 probation_len: usize,
210 cms: Cms,
211 doorkeeper: Doorkeeper,
212 admissions: u64,
213 rejections: u64,
214}
215
216impl<K: Hash + Eq + Clone, V> TinyLfuCache<K, V> {
217 pub fn with_capacity(c: usize) -> Self {
218 let c = c.max(4);
219 let window_cap = (c / 100).max(1);
221 let main = c - window_cap;
222 let protected_cap = (main * 4 / 5).max(1);
223 let probation_cap = (main - protected_cap).max(1);
224 let cms_cells = (c * 4).max(64);
225 let sample_size = (10 * c as u64).max(64);
226 Self {
227 c,
228 window_cap,
229 protected_cap,
230 probation_cap,
231 nodes: Vec::new(),
232 free: Vec::new(),
233 index: HashMap::new(),
234 window_head: NIL,
235 window_tail: NIL,
236 window_len: 0,
237 protected_head: NIL,
238 protected_tail: NIL,
239 protected_len: 0,
240 probation_head: NIL,
241 probation_tail: NIL,
242 probation_len: 0,
243 cms: Cms::new(cms_cells, sample_size),
244 doorkeeper: Doorkeeper::new(c * 8),
245 admissions: 0,
246 rejections: 0,
247 }
248 }
249
250 pub fn capacity(&self) -> usize {
251 self.c
252 }
253 pub fn len(&self) -> usize {
254 self.window_len + self.protected_len + self.probation_len
255 }
256 pub fn is_empty(&self) -> bool {
257 self.len() == 0
258 }
259 pub fn admissions(&self) -> u64 {
260 self.admissions
261 }
262 pub fn rejections(&self) -> u64 {
263 self.rejections
264 }
265 pub fn window_len(&self) -> usize {
266 self.window_len
267 }
268 pub fn protected_len(&self) -> usize {
269 self.protected_len
270 }
271 pub fn probation_len(&self) -> usize {
272 self.probation_len
273 }
274
275 fn record_access(&mut self, hash: u64) {
276 if self.doorkeeper.check_or_add(hash) {
279 self.cms.increment(hash);
280 if self.cms.additions == 0 {
283 self.doorkeeper.clear();
284 }
285 }
286 }
287
288 pub fn get(&mut self, key: &K) -> Option<&V> {
289 let h = hash_one(key);
290 self.record_access(h);
291 let id = *self.index.get(key)?;
292 let segment = self.nodes[id as usize].as_ref().unwrap().list;
293 match segment {
294 Segment::Window => {
295 self.unlink(id);
296 self.push_front_window(id);
297 }
298 Segment::Probation => {
299 self.unlink(id);
301 self.probation_len -= 1;
302 if self.protected_len >= self.protected_cap {
303 let demote = self.protected_tail;
305 self.unlink(demote);
306 self.protected_len -= 1;
307 self.nodes[demote as usize].as_mut().unwrap().list = Segment::Probation;
308 self.push_front_probation(demote);
309 self.probation_len += 1;
310 }
311 self.nodes[id as usize].as_mut().unwrap().list = Segment::Protected;
312 self.push_front_protected(id);
313 self.protected_len += 1;
314 }
315 Segment::Protected => {
316 self.unlink(id);
317 self.push_front_protected(id);
318 }
319 }
320 self.nodes[id as usize].as_ref().map(|n| &n.value)
321 }
322
323 pub fn put(&mut self, key: K, value: V) -> Option<(K, V)> {
325 let h = hash_one(&key);
326 self.record_access(h);
327
328 if let Some(&id) = self.index.get(&key) {
329 let n = self.nodes[id as usize].as_mut().unwrap();
330 n.value = value;
331 let seg = n.list;
332 self.unlink(id);
333 match seg {
334 Segment::Window => self.push_front_window(id),
335 Segment::Probation => self.push_front_probation(id),
336 Segment::Protected => self.push_front_protected(id),
337 }
338 return None;
339 }
340
341 if self.window_len < self.window_cap {
343 let id = self.alloc(Node {
344 key: key.clone(),
345 value,
346 prev: NIL,
347 next: NIL,
348 list: Segment::Window,
349 });
350 self.index.insert(key, id);
351 self.push_front_window(id);
352 self.window_len += 1;
353 return None;
354 }
355
356 let candidate_id = self.window_tail;
359 self.unlink(candidate_id);
360 self.window_len -= 1;
361
362 let new_id = self.alloc(Node {
364 key: key.clone(),
365 value,
366 prev: NIL,
367 next: NIL,
368 list: Segment::Window,
369 });
370 self.index.insert(key, new_id);
371 self.push_front_window(new_id);
372 self.window_len += 1;
373
374 if self.probation_len < self.probation_cap {
376 self.nodes[candidate_id as usize].as_mut().unwrap().list = Segment::Probation;
378 self.push_front_probation(candidate_id);
379 self.probation_len += 1;
380 self.admissions += 1;
381 return None;
382 }
383
384 let victim_id = self.probation_tail;
386 let cand_hash = hash_one(&self.nodes[candidate_id as usize].as_ref().unwrap().key);
387 let vic_hash = hash_one(&self.nodes[victim_id as usize].as_ref().unwrap().key);
388 let cand_freq = self.cms.estimate(cand_hash);
389 let vic_freq = self.cms.estimate(vic_hash);
390
391 if cand_freq >= vic_freq {
392 self.unlink(victim_id);
394 self.probation_len -= 1;
395 let v = self.nodes[victim_id as usize].take().unwrap();
396 self.index.remove(&v.key);
397 self.free.push(victim_id);
398
399 self.nodes[candidate_id as usize].as_mut().unwrap().list = Segment::Probation;
400 self.push_front_probation(candidate_id);
401 self.probation_len += 1;
402 self.admissions += 1;
403 Some((v.key, v.value))
404 } else {
405 let c = self.nodes[candidate_id as usize].take().unwrap();
407 self.index.remove(&c.key);
408 self.free.push(candidate_id);
409 self.rejections += 1;
410 Some((c.key, c.value))
411 }
412 }
413
414 fn alloc(&mut self, n: Node<K, V>) -> u32 {
415 if let Some(id) = self.free.pop() {
416 self.nodes[id as usize] = Some(n);
417 id
418 } else {
419 let id = self.nodes.len() as u32;
420 self.nodes.push(Some(n));
421 id
422 }
423 }
424
425 fn unlink(&mut self, id: u32) {
426 let (prev, next, seg) = {
427 let n = self.nodes[id as usize].as_ref().unwrap();
428 (n.prev, n.next, n.list)
429 };
430 if prev != NIL {
431 self.nodes[prev as usize].as_mut().unwrap().next = next;
432 }
433 if next != NIL {
434 self.nodes[next as usize].as_mut().unwrap().prev = prev;
435 }
436 let n = self.nodes[id as usize].as_mut().unwrap();
437 n.prev = NIL;
438 n.next = NIL;
439 match seg {
440 Segment::Window => {
441 if self.window_head == id {
442 self.window_head = next;
443 }
444 if self.window_tail == id {
445 self.window_tail = prev;
446 }
447 }
448 Segment::Probation => {
449 if self.probation_head == id {
450 self.probation_head = next;
451 }
452 if self.probation_tail == id {
453 self.probation_tail = prev;
454 }
455 }
456 Segment::Protected => {
457 if self.protected_head == id {
458 self.protected_head = next;
459 }
460 if self.protected_tail == id {
461 self.protected_tail = prev;
462 }
463 }
464 }
465 }
466
467 fn push_front_window(&mut self, id: u32) {
468 let old = self.window_head;
469 let n = self.nodes[id as usize].as_mut().unwrap();
470 n.next = old;
471 n.prev = NIL;
472 if old != NIL {
473 self.nodes[old as usize].as_mut().unwrap().prev = id;
474 }
475 self.window_head = id;
476 if self.window_tail == NIL {
477 self.window_tail = id;
478 }
479 }
480 fn push_front_protected(&mut self, id: u32) {
481 let old = self.protected_head;
482 let n = self.nodes[id as usize].as_mut().unwrap();
483 n.next = old;
484 n.prev = NIL;
485 if old != NIL {
486 self.nodes[old as usize].as_mut().unwrap().prev = id;
487 }
488 self.protected_head = id;
489 if self.protected_tail == NIL {
490 self.protected_tail = id;
491 }
492 }
493 fn push_front_probation(&mut self, id: u32) {
494 let old = self.probation_head;
495 let n = self.nodes[id as usize].as_mut().unwrap();
496 n.next = old;
497 n.prev = NIL;
498 if old != NIL {
499 self.nodes[old as usize].as_mut().unwrap().prev = id;
500 }
501 self.probation_head = id;
502 if self.probation_tail == NIL {
503 self.probation_tail = id;
504 }
505 }
506}
507
508fn hash_one<K: Hash>(k: &K) -> u64 {
509 let mut h = FnvHasher::new();
510 k.hash(&mut h);
511 h.finish()
512}
513
514struct FnvHasher(u64);
515impl FnvHasher {
516 fn new() -> Self {
517 FnvHasher(FNV_OFFSET)
518 }
519}
520impl Hasher for FnvHasher {
521 fn finish(&self) -> u64 {
522 self.0
523 }
524 fn write(&mut self, bytes: &[u8]) {
525 for &b in bytes {
526 self.0 ^= b as u64;
527 self.0 = self.0.wrapping_mul(FNV_PRIME);
528 }
529 }
530}
531
532#[cfg(test)]
533#[path = "tinylfu_tests.rs"]
534mod tests;