Feature: policy-lfu
Evict the entry with the lowest access frequency; break ties by recency (common) or arbitrarily.
The typical O(1) LFU design is:
HashMap<K, EntryId>to locate entries- Per-entry metadata:
freq, plus pointers for an intrusive list inside its frequency bucket HashMap<freq, BucketList>(orVec<BucketList>with bounded freq) storing LRU order within each frequencymin_freqtracking the smallest frequency currently present
In cachekit, src/policy/lfu.rs implements LFU with O(1) eviction based on bucket + min_freq.
- Return value and increment its frequency:
- Remove entry from old frequency bucket list.
- Increment
freq. - Insert into new bucket list (typically at head as MRU within that freq).
- If old bucket becomes empty and was
min_freq, incrementmin_freq.
- If present: update value and treat as an access (commonly increment frequency).
- Else:
- If full: evict from
min_freqbucket (usually the LRU within that bucket). - Insert new entry with
freq = 1and setmin_freq = 1.
- If full: evict from
- Pop from the
min_freqbucket; if it becomes empty, advancemin_freq.
- Lookup: O(1)
- Frequency bump: O(1)
- Eviction: O(1)
- Metadata: higher than LRU/FIFO due to frequency buckets and per-entry freq
Pure LFU can keep “once-hot” items forever. To avoid this, add one of:
- Periodic global decay: divide all frequencies by 2 each interval (O(n) unless amortized)
- Time-decayed score: maintain
score = freq / f(age)(more complex) - Windowed LFU: only count accesses in a moving time window
See lfu-aging.md for common patterns.