The LRU eviction policy tracks the last access time for each entry and identifies candidates for eviction based on recency. Entries that haven't been accessed recently are considered "cold" and prioritized for removal.
Formula: Eviction score = last_accessed.elapsed()
Eviction Strategy: Highest score (longest time since access) evicted first.
struct EntryMetadata {
last_accessed: Instant,
}
Size: 16 bytes (Instant = 2×u64 on most platforms)
pub struct Lru<D> {
inner: D,
metadata: Arc<RwLock<HashMap<String, EntryMetadata>>>,
}
Thread-Safety: RwLock allows concurrent reads, serialized writes.
impl<D: MappedDictionary<V>, V> MappedDictionary<V> for Lru<D> {
fn get_value(&self, term: &str) -> Option<V> {
// Update metadata
{
let mut metadata = self.metadata.write().unwrap();
metadata
.entry(term.to_string())
.and_modify(|m| m.update_access())
.or_insert_with(EntryMetadata::new);
}
// Forward to inner dictionary
self.inner.get_value(term)
}
}
Complexity: $\mathcal{O}(1)$ metadata update + $\mathcal{O}(d)$ inner lookup
impl<D> Lru<D> {
pub fn find_lru(&self, candidates: &[&str]) -> Option<String> {
let metadata = self.metadata.read().unwrap();
candidates
.iter()
.filter_map(|term| {
metadata.get(*term).map(|m| (*term, m.recency()))
})
.max_by_key(|(_, recency)| *recency)
.map(|(term, _)| term.to_string())
}
}
Complexity: $\mathcal{O}(n)$ where n = number of candidates
use liblevenshtein::cache::eviction::Lru;
use liblevenshtein::prelude::*;
use std::thread;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let dict = PathMapDictionary::from_terms_with_values([
("active", 1),
("dormant", 2),
("cold", 3),
]);
let lru = Lru::new(dict);
// Access pattern: active (3×), dormant (1×), cold (0×)
lru.get_value("active");
thread::sleep(Duration::from_millis(10));
lru.get_value("active");
thread::sleep(Duration::from_millis(10));
lru.get_value("dormant");
thread::sleep(Duration::from_millis(10));
lru.get_value("active");
// Find LRU: "cold" (never accessed)
let lru_entry = lru.find_lru(&["active", "dormant", "cold"]);
assert_eq!(lru_entry, Some("cold".to_string()));
Ok(())
}
use liblevenshtein::cache::eviction::Lru;
use liblevenshtein::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Identifiers in current scope
let identifiers = PathMapDictionary::from_terms_with_values([
("count", "i32"),
("scratch", "String"),
("result", "Vec<u8>"),
("old_variable", "bool"),
]);
let lru = Lru::new(identifiers);
// User types code, referencing some identifiers
lru.get_value("count"); // Used in loop
lru.get_value("result"); // Used for accumulation
lru.get_value("count"); // Used again
// "old_variable" never accessed
// When memory pressure high, evict LRU
let candidates = vec!["count", "scratch", "result", "old_variable"];
let evict = lru.find_lru(&candidates);
assert_eq!(evict, Some("old_variable".to_string()));
println!("Evict: {:?} (least recently used)", evict);
Ok(())
}
use liblevenshtein::cache::eviction::Lru;
use liblevenshtein::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Session tokens mapped to user IDs
let sessions = PathMapDictionary::from_terms_with_values([
("session_abc123", "user_42"),
("session_def456", "user_99"),
("session_ghi789", "user_12"),
]);
let lru_sessions = Lru::new(sessions);
// Simulate user activity
lru_sessions.get_value("session_abc123"); // User 42 active
lru_sessions.get_value("session_def456"); // User 99 active
// User 12 inactive (no access)
// When cache full, evict least recently used session
let candidates = vec!["session_abc123", "session_def456", "session_ghi789"];
let evict_session = lru_sessions.find_lru(&candidates);
assert_eq!(evict_session, Some("session_ghi789".to_string()));
println!("Evict session: {:?}", evict_session);
Ok(())
}
use liblevenshtein::cache::eviction::{Lru, Ttl};
use liblevenshtein::prelude::*;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let dict = PathMapDictionary::from_terms_with_values([
("foo", 1),
("bar", 2),
]);
// Compose TTL + LRU
let ttl = Ttl::new(dict, Duration::from_secs(300));
let lru = Lru::new(ttl);
// Access "foo" (recent)
lru.get_value("foo");
// "bar" not accessed (LRU)
// Find LRU among non-expired entries
let lru_entry = lru.find_lru(&["foo", "bar"]);
assert_eq!(lru_entry, Some("bar".to_string()));
Ok(())
}
| Operation | Complexity | Notes |
|---|---|---|
| get_value | $\mathcal{O}(d)$ + $\mathcal{O}(1)$ | Dictionary lookup + metadata update |
| contains | $\mathcal{O}(d)$ + $\mathcal{O}(1)$ | Dictionary check + metadata update |
| find_lru | $\mathcal{O}(n)$ | Linear scan of candidates |
Where:
d = inner dictionary operation complexityn = number of candidatesPer-Entry Overhead: 16 bytes (Instant)
Total Metadata: 16n bytes for n tracked entries
Comparison:
RwLock Behavior:
High-Concurrency Impact:
Mitigation:
| Aspect | LRU | LFU | Age | TTL |
|---|---|---|---|---|
| Metric | Recency | Frequency | Insertion order | Time-to-live |
| Overhead | 16 bytes | 8 bytes | 16 bytes | 16 bytes |
| Best for | Access patterns | Hot content | FIFO queues | Expiration |
| Eviction | Longest elapsed | Lowest count | Oldest insert | Expired only |
✅ Good For:
\approx$ likely to reuse)❌ Not Ideal For:
src/cache/eviction/lru.rsuse liblevenshtein::cache::eviction::Lru;
use liblevenshtein::prelude::*;
// Basic usage
let dict = PathMapDictionary::from_terms_with_values([("key", "value")]);
let lru = Lru::new(dict);
lru.get_value("key");
// Find LRU entry
let lru_entry = lru.find_lru(&["key1", "key2"]);
// Composition with TTL
use std::time::Duration;
let ttl = Ttl::new(dict, Duration::from_secs(300));
let lru_ttl = Lru::new(ttl);
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |