Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ policy-all = [
"policy-two-q",
"policy-s3-fifo",
"policy-arc",
"policy-car",
"policy-lifo",
"policy-mfu",
"policy-mru",
Expand All @@ -67,6 +68,7 @@ policy-heap-lfu = []
policy-two-q = []
policy-s3-fifo = []
policy-arc = []
policy-car = []
policy-lifo = []
policy-mfu = []
policy-mru = []
Expand Down
47 changes: 23 additions & 24 deletions docs/policies/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,25 @@ If you can only implement one “general purpose” policy for mixed workloads,

### Implemented Policies (CacheKit)

Enable the corresponding feature flag for each policy. See [Compatibility and Features](../guides/compatibility-and-features.md).

| Policy | Feature | Summary | Doc |
|--------|---------|---------|-----|
| LRU | `policy-lru` | Strong default for temporal locality | [LRU doc](lru.md) |
| Fast LRU | `policy-fast-lru` | Optimized single-threaded LRU | — |
| MRU | `policy-mru` | Evicts most recent (niche: cyclic patterns) | [MRU doc](mru.md) |
| SLRU | `policy-slru` | Segmented LRU with probation/protected | [SLRU doc](slru.md) |
| LFU | `policy-lfu` | Frequency-driven, stable hot sets | [LFU doc](lfu.md) |
| Heap-LFU | `policy-heap-lfu` | LFU with heap eviction | [Heap-LFU doc](heap-lfu.md) |
| MFU | `policy-mfu` | Evicts highest frequency (niche/baseline) | [MFU doc](mfu.md) |
| LRU-K | `policy-lru-k` | Scan-resistant recency | [LRU-K doc](lru-k.md) |
| 2Q | `policy-two-q` | Probation + protected queues | [2Q doc](2q.md) |
| ARC | `policy-arc` | Adaptive recency/frequency balance | [ARC doc](arc.md) |
| FIFO | `policy-fifo` | Simple insertion-order (oldest first) | [FIFO doc](fifo.md) |
| LIFO | `policy-lifo` | Stack-based (newest first) | [LIFO doc](lifo.md) |
| Clock | `policy-clock` | Approximate LRU | [Clock doc](clock.md) |
| Clock-PRO | `policy-clock-pro` | Scan-resistant Clock variant | [Clock-PRO doc](clock-pro.md) |
| NRU | `policy-nru` | Coarse recency tracking | [NRU doc](nru.md) |
| S3-FIFO | `policy-s3-fifo` | Scan-resistant FIFO | [S3-FIFO doc](s3-fifo.md) |
| Random | `policy-random` | Baseline: uniform random eviction | [Random doc](random.md) |
| Policy | Summary | Doc |
|--------|---------|-----|
| LRU | Strong default for temporal locality | [LRU doc](lru.md) |
| MRU | Evicts most recent (niche: cyclic patterns) | [MRU doc](mru.md) |
| SLRU | Segmented LRU with probation/protected | [SLRU doc](slru.md) |
| LFU | Frequency-driven, stable hot sets | [LFU doc](lfu.md) |
| Heap-LFU | LFU with heap eviction | [Heap-LFU doc](heap-lfu.md) |
| MFU | Evicts highest frequency (niche/baseline) | [MFU doc](mfu.md) |
| LRU-K | Scan-resistant recency | [LRU-K doc](lru-k.md) |
| 2Q | Probation + protected queues | [2Q doc](2q.md) |
| ARC | Adaptive recency/frequency balance | [ARC doc](arc.md) |
| CAR | ARC-like with Clock (lower hit overhead) | [CAR doc](car.md) |
| FIFO | Simple insertion-order (oldest first) | [FIFO doc](fifo.md) |
| LIFO | Stack-based (newest first) | [LIFO doc](lifo.md) |
| Clock | Approximate LRU | [Clock doc](clock.md) |
| Clock-PRO | Scan-resistant Clock variant | [Clock-PRO doc](clock-pro.md) |
| NRU | Coarse recency tracking | [NRU doc](nru.md) |
| S3-FIFO | Scan-resistant FIFO | [S3-FIFO doc](s3-fifo.md) |
| Random | Baseline: uniform random eviction | [Random doc](random.md) |

### Roadmap Policies (Planned)

Expand All @@ -73,6 +71,7 @@ See [Policy roadmap](roadmap/README.md) for upcoming policies (LIRS, GDSF, TinyL
- **LRU-K**: Good scan resistance; more metadata per entry.
- **2Q**: Simple scan resistance; requires queue sizing.
- **ARC**: Adaptive recency/frequency balance; no manual tuning; more metadata overhead.
- **CAR**: ARC-like adaptivity with Clock; hits set ref bit only; scan-resistant.
- **FIFO**: Predictable insertion order (oldest first); weak under strong locality.
- **LIFO**: Stack order (newest first); niche use for undo buffers.
- **Clock-PRO**: Scan-resistant Clock variant; more complexity.
Expand All @@ -83,10 +82,10 @@ For broader policy taxonomy (OPT, ARC, CAR, LIRS, Random, etc.), use the

## Practical Tradeoffs (What Changes In Real Systems)

- **Scan resistance**: `LRU`/`Clock` are vulnerable; `S3-FIFO`, `Heap-LFU`, `LRU-K`, `2Q`, and `ARC` handle scans better.
- **Metadata & CPU**: `Random`/`FIFO` < `Clock` < `LRU` < `2Q`/`SLRU` < `LRU-K`/`ARC`/`LIRS`.
- **Scan resistance**: `LRU`/`Clock` are vulnerable; `S3-FIFO`, `Heap-LFU`, `LRU-K`, `2Q`, `ARC`, and `CAR` handle scans better.
- **Metadata & CPU**: `Random`/`FIFO` < `Clock` < `LRU` < `2Q`/`SLRU` < `LRU-K`/`ARC`/`CAR`/`LIRS`.
- **Concurrency**: strict global `LRU` lists can contend; `Clock` and sharded designs often scale better.
- **Adaptivity**: `LFU` needs decay to adapt; `ARC`-family adapts via history; static partitions (`2Q`/`SLRU`) need tuning.
- **Adaptivity**: `LFU` needs decay to adapt; `ARC`/`CAR` adapt via history; static partitions (`2Q`/`SLRU`) need tuning.
- **Predictability**: simpler policies are easier to reason about under tail-latency constraints; complex policies can have more edge cases.

## When To Use / Not Use (Rules Of Thumb)
Expand Down
114 changes: 114 additions & 0 deletions docs/policies/car.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# CAR (Clock with Adaptive Replacement)

## Status

**Implemented** in `src/policy/car.rs`

## Goal

ARC-like adaptivity with Clock mechanics to reduce list manipulation overhead.
Hits only set a reference bit instead of moving entries in linked lists,
improving concurrency and cache-friendliness.

## Core Idea

Replace ARC's LRU lists with Clock structures plus ghost history:

- Two Clock rings for resident sets: **Recent** (seen once) and **Frequent** (repeated access)
- Reference bits approximate recency within each set
- ARC-like feedback from ghost hits adjusts `target_recent_size` (the adaptation parameter)
- On hit: set ref bit only (no list move)
- On eviction: sweep with clock hand, ref=0 evict, ref=1 clear and continue

## Core Data Structures

- `HashMap<K, usize>` for key → slot index
- Single slot array with metadata: key, value, referenced bit, `Ring` (Recent/Frequent)
- Two clock hands (`hand_recent`, `hand_frequent`) walking per-ring intrusive circular lists
- Ghost lists `ghost_recent`, `ghost_frequent` (keys only) via `GhostList<K>`

## Key Operations (High Level)

### Get Operation

- Hit in Recent or Frequent ring: set `referenced = true`, return value (no list move)
- Miss: not in cache (see insert for ghost hit handling)

### Insert Operation

- Key in cache: update value, set ref, return old value
- Ghost hit in `ghost_recent`: adapt (increase `target_recent_size`), evict if needed, insert into Frequent ring
- Ghost hit in `ghost_frequent`: adapt (decrease `target_recent_size`), evict if needed, insert into Frequent ring
- Miss: evict if full (replace step), insert into Recent ring

### Replacement Step

Loop until one entry is evicted:

- If `|Recent| > target_recent_size`: sweep the Recent ring
- Ref=0: evict to `ghost_recent`, done
- Ref=1: demote to Frequent ring (clear ref), continue
- If `|Recent| ≤ target_recent_size`: sweep the Frequent ring
- Ref=0: evict to `ghost_frequent`, done
- Ref=1: clear ref, advance hand, continue

### Adaptation

Same as ARC: ghost hit in `ghost_recent` increases `target_recent_size`;
ghost hit in `ghost_frequent` decreases it.

## Complexity & Overhead

- O(1) get (hash lookup + bit set)
- O(1) amortized insert
- More metadata than plain Clock due to ghost lists and dual rings
- Lower overhead than ARC on hits (no list moves)

## Example Usage

```rust
use cachekit::policy::car::CARCore;
use cachekit::traits::{CoreCache, ReadOnlyCache};

let mut cache = CARCore::new(100);
cache.insert("page1", "content1");
cache.insert("page2", "content2");
assert_eq!(cache.get(&"page1"), Some(&"content1"));
println!("recent: {}, frequent: {}, target: {}", cache.recent_len(), cache.frequent_len(), cache.target_recent_size());
```

## When To Use

- Mixed workloads with unknown recency/frequency balance
- Need scan resistance (ghost lists) like ARC
- Prefer lower hit overhead than ARC (bit set vs list move)
- Concurrency-friendly hit path

## When NOT To Use

- Simple temporal locality (Clock or LRU are simpler)
- Memory-constrained (ghost lists add overhead)
- Known workload where tuned 2Q/SLRU suffices

## Performance Characteristics

| Metric | Value |
|----------|------------------|
| Get | O(1) |
| Insert | O(1) amortized |
| Remove | O(1) |
| Space | O(n) + ghost keys|
| Scan res | High |
| Adaptivity | Self-tuning |

## Implementation Notes

- Uses single slot array; Recent/Frequent are per-ring intrusive circular linked lists with separate hands
- Demotion from Recent to Frequent clears ref bit (per CAR paper)
- Ghost lists bounded to `capacity` each
- `target_recent_size` starts at `capacity / 2`

## References

- Bansal & Modha, "CAR: Clock with Adaptive Replacement", FAST 2004
- Wikipedia: https://en.wikipedia.org/wiki/Cache_replacement_policies
1 change: 0 additions & 1 deletion docs/policies/roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ If a policy moves into production code, its document should be moved back to

## Roadmap Policies

- [CAR](car.md)
- [GDSF](gdsf.md)
- [LFU Aging](lfu-aging.md)
- [LIRS](lirs.md)
Expand Down
24 changes: 0 additions & 24 deletions docs/policies/roadmap/car.md

This file was deleted.

Loading