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
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions benches/workloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,13 @@ fn bench_hit_rates(c: &mut Criterion) {
&mut generator,
OPS,
&mut op_model,
|k| Arc::clone(&value_pool[k as usize]),
|k| {
Arc::clone(
value_pool
.get(k as usize)
.expect("generator produced key outside configured UNIVERSE"),
)
},
);
total += start.elapsed();
}
Expand Down Expand Up @@ -214,7 +220,16 @@ fn bench_comprehensive(c: &mut Criterion) {
policy_id,
&mut cache,
cfg,
|key| Arc::clone(&value_pool[key as usize]),
|key| {
let idx = key as usize;
Arc::clone(value_pool.get(idx).unwrap_or_else(|| {
panic!(
"workload key {} out of range for value_pool (len {}, expected < UNIVERSE)",
key,
value_pool.len()
)
}))
},
);
total += start.elapsed();
}
Expand Down
10 changes: 4 additions & 6 deletions src/ds/intrusive_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -871,11 +871,10 @@ impl<T> IntrusiveList<T> {
#[inline]
fn attach_front(&mut self, id: SlotId) -> Option<()> {
let old_head = self.head;
if let Some(node) = self.arena.get_mut(id) {
{
let node = self.arena.get_mut(id)?;
node.prev = None;
node.next = old_head;
} else {
return None;
}
if let Some(old_head) = old_head {
if let Some(head_node) = self.arena.get_mut(old_head) {
Expand All @@ -891,11 +890,10 @@ impl<T> IntrusiveList<T> {
#[inline]
fn attach_back(&mut self, id: SlotId) -> Option<()> {
let old_tail = self.tail;
if let Some(node) = self.arena.get_mut(id) {
{
let node = self.arena.get_mut(id)?;
node.next = None;
node.prev = old_tail;
} else {
return None;
}
if let Some(old_tail) = old_tail {
if let Some(tail_node) = self.arena.get_mut(old_tail) {
Expand Down
2 changes: 2 additions & 0 deletions src/policy/s3_fifo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,8 @@ where
/// hit/miss counters.
#[cfg(feature = "concurrency")]
#[inline]
// `try_update` is the nightly name, but `fetch_update` is required by our Rust 1.85 MSRV.
#[allow(deprecated)]
pub(crate) fn get_shared(&self, key: &K) -> Option<&V> {
let &id = self.map.get(key)?;
let node = self.arena.get(id).expect("map/arena out of sync");
Expand Down
17 changes: 9 additions & 8 deletions tests/abstract_models/reference/lfu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! **Source:** [`docs/testing/specs/policies/exact/lfu.md`](../../../docs/testing/specs/policies/exact/lfu.md) ·
//! [matrix.md](../../../docs/testing/specs/matrix.md)
//! **Tier:** reference (spec-first oracle).
//! **Formulation:** `HashMap<K, u64>` + append-only `first_seen` log for FIFO tie-break;
//! **Formulation:** `HashMap<K, u64>` + append-only bucket-arrival log for FIFO tie-break;
//! independent of [`FrequencyBuckets`](cachekit::ds::FrequencyBuckets).

use std::collections::{HashMap, HashSet, VecDeque};
Expand All @@ -14,7 +14,7 @@ use crate::abstract_models::{HitMiss, ModelStep, Op, OracleExpectation, PolicyMo
#[derive(Debug, Clone)]
pub struct NaiveLfuModel<K> {
freq: HashMap<K, u64>,
first_seen: VecDeque<K>,
bucket_arrivals: VecDeque<K>,
capacity: usize,
}

Expand All @@ -25,7 +25,7 @@ where
pub fn new(capacity: usize) -> Self {
Self {
freq: HashMap::new(),
first_seen: VecDeque::new(),
bucket_arrivals: VecDeque::new(),
capacity,
}
}
Expand All @@ -42,9 +42,9 @@ where
self.freq.values().copied().min()
}

/// Last append index for `key` in the tie-break log (re-inserts append again).
fn last_seen_index(&self, key: &K) -> usize {
self.first_seen
/// Last bucket-arrival index for `key` (touches and re-inserts append again).
fn bucket_arrival_index(&self, key: &K) -> usize {
self.bucket_arrivals
.iter()
.rposition(|k| k == key)
.unwrap_or(usize::MAX)
Expand All @@ -56,7 +56,7 @@ where
.iter()
.filter(|(_, f)| **f == min)
.map(|(k, _)| k)
.min_by_key(|k| self.last_seen_index(k))
.min_by_key(|k| self.bucket_arrival_index(k))
.cloned()
}

Expand All @@ -69,6 +69,7 @@ where
fn touch(&mut self, key: &K) -> bool {
if let Some(f) = self.freq.get_mut(key) {
*f = f.saturating_add(1);
self.bucket_arrivals.push_back(key.clone());
true
} else {
false
Expand All @@ -84,7 +85,7 @@ where
evicted = self.evict_victim();
}
self.freq.insert(key.clone(), 1);
self.first_seen.push_back(key);
self.bucket_arrivals.push_back(key);
evicted
}

Expand Down
14 changes: 14 additions & 0 deletions tests/policy_semantics/lfu_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,20 @@ fn smoke_lfu_naive_agreement() {
assert_models_agree(&mut naive, &mut current, &ops);
}

#[test]
fn lfu_naive_agreement_uses_bucket_arrival_for_ties() {
let ops = [
Op::Insert(1),
Op::Insert(2),
Op::Touch(2),
Op::Touch(1),
Op::EvictOne,
];
let mut naive = NaiveLfuModel::new(2);
let mut current = LfuModel::new(2);
assert_models_agree(&mut naive, &mut current, &ops);
}

#[test]
fn smoke_lfu() {
let ops = [
Expand Down
Loading