|
| 1 | +# CAR (Clock with Adaptive Replacement) |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +**Implemented** in `src/policy/car.rs` |
| 6 | + |
| 7 | +## Goal |
| 8 | + |
| 9 | +ARC-like adaptivity with Clock mechanics to reduce list manipulation overhead. |
| 10 | +Hits only set a reference bit instead of moving entries in linked lists, |
| 11 | +improving concurrency and cache-friendliness. |
| 12 | + |
| 13 | +## Core Idea |
| 14 | + |
| 15 | +Replace ARC's LRU lists with Clock structures plus ghost history: |
| 16 | + |
| 17 | +- Two Clock rings for resident sets: **Recent** (seen once) and **Frequent** (repeated access) |
| 18 | +- Reference bits approximate recency within each set |
| 19 | +- ARC-like feedback from ghost hits adjusts `target_recent_size` (the adaptation parameter) |
| 20 | +- On hit: set ref bit only (no list move) |
| 21 | +- On eviction: sweep with clock hand, ref=0 evict, ref=1 clear and continue |
| 22 | + |
| 23 | +## Core Data Structures |
| 24 | + |
| 25 | +- `HashMap<K, usize>` for key → slot index |
| 26 | +- Single slot array with metadata: key, value, referenced bit, `Ring` (Recent/Frequent) |
| 27 | +- Two clock hands (`hand_recent`, `hand_frequent`) walking per-ring intrusive circular lists |
| 28 | +- Ghost lists `ghost_recent`, `ghost_frequent` (keys only) via `GhostList<K>` |
| 29 | + |
| 30 | +## Key Operations (High Level) |
| 31 | + |
| 32 | +### Get Operation |
| 33 | + |
| 34 | +- Hit in Recent or Frequent ring: set `referenced = true`, return value (no list move) |
| 35 | +- Miss: not in cache (see insert for ghost hit handling) |
| 36 | + |
| 37 | +### Insert Operation |
| 38 | + |
| 39 | +- Key in cache: update value, set ref, return old value |
| 40 | +- Ghost hit in `ghost_recent`: adapt (increase `target_recent_size`), evict if needed, insert into Frequent ring |
| 41 | +- Ghost hit in `ghost_frequent`: adapt (decrease `target_recent_size`), evict if needed, insert into Frequent ring |
| 42 | +- Miss: evict if full (replace step), insert into Recent ring |
| 43 | + |
| 44 | +### Replacement Step |
| 45 | + |
| 46 | +Loop until one entry is evicted: |
| 47 | + |
| 48 | +- If `|Recent| > target_recent_size`: sweep the Recent ring |
| 49 | + - Ref=0: evict to `ghost_recent`, done |
| 50 | + - Ref=1: demote to Frequent ring (clear ref), continue |
| 51 | +- If `|Recent| ≤ target_recent_size`: sweep the Frequent ring |
| 52 | + - Ref=0: evict to `ghost_frequent`, done |
| 53 | + - Ref=1: clear ref, advance hand, continue |
| 54 | + |
| 55 | +### Adaptation |
| 56 | + |
| 57 | +Same as ARC: ghost hit in `ghost_recent` increases `target_recent_size`; |
| 58 | +ghost hit in `ghost_frequent` decreases it. |
| 59 | + |
| 60 | +## Complexity & Overhead |
| 61 | + |
| 62 | +- O(1) get (hash lookup + bit set) |
| 63 | +- O(1) amortized insert |
| 64 | +- More metadata than plain Clock due to ghost lists and dual rings |
| 65 | +- Lower overhead than ARC on hits (no list moves) |
| 66 | + |
| 67 | +## Example Usage |
| 68 | + |
| 69 | +```rust |
| 70 | +use cachekit::policy::car::CARCore; |
| 71 | +use cachekit::traits::{CoreCache, ReadOnlyCache}; |
| 72 | + |
| 73 | +let mut cache = CARCore::new(100); |
| 74 | +cache.insert("page1", "content1"); |
| 75 | +cache.insert("page2", "content2"); |
| 76 | +assert_eq!(cache.get(&"page1"), Some(&"content1")); |
| 77 | +println!("recent: {}, frequent: {}, target: {}", cache.recent_len(), cache.frequent_len(), cache.target_recent_size()); |
| 78 | +``` |
| 79 | + |
| 80 | +## When To Use |
| 81 | + |
| 82 | +- Mixed workloads with unknown recency/frequency balance |
| 83 | +- Need scan resistance (ghost lists) like ARC |
| 84 | +- Prefer lower hit overhead than ARC (bit set vs list move) |
| 85 | +- Concurrency-friendly hit path |
| 86 | + |
| 87 | +## When NOT To Use |
| 88 | + |
| 89 | +- Simple temporal locality (Clock or LRU are simpler) |
| 90 | +- Memory-constrained (ghost lists add overhead) |
| 91 | +- Known workload where tuned 2Q/SLRU suffices |
| 92 | + |
| 93 | +## Performance Characteristics |
| 94 | + |
| 95 | +| Metric | Value | |
| 96 | +|----------|------------------| |
| 97 | +| Get | O(1) | |
| 98 | +| Insert | O(1) amortized | |
| 99 | +| Remove | O(1) | |
| 100 | +| Space | O(n) + ghost keys| |
| 101 | +| Scan res | High | |
| 102 | +| Adaptivity | Self-tuning | |
| 103 | + |
| 104 | +## Implementation Notes |
| 105 | + |
| 106 | +- Uses single slot array; Recent/Frequent are per-ring intrusive circular linked lists with separate hands |
| 107 | +- Demotion from Recent to Frequent clears ref bit (per CAR paper) |
| 108 | +- Ghost lists bounded to `capacity` each |
| 109 | +- `target_recent_size` starts at `capacity / 2` |
| 110 | + |
| 111 | +## References |
| 112 | + |
| 113 | +- Bansal & Modha, "CAR: Clock with Adaptive Replacement", FAST 2004 |
| 114 | +- Wikipedia: https://en.wikipedia.org/wiki/Cache_replacement_policies |
0 commit comments