Skip to content

Commit

Permalink
Support weight-based (cost-based) eviction and unbound cache
Browse files Browse the repository at this point in the history
- Implement aggregated victims strategy for cost-based eviction.
- Rename max_entry back to max_capacity, and remove an optional max_weight
  field from InnerCache.
  • Loading branch information
tatsuya6502 committed Aug 15, 2021
1 parent 6a5444e commit 2835b94
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 199 deletions.
4 changes: 4 additions & 0 deletions src/common/deque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ impl<T> DeqNode<T> {
element,
}
}

pub(crate) fn next_node(&self) -> Option<&DeqNode<T>> {
self.next.as_ref().map(|node| unsafe { node.as_ref() })
}
}

/// Cursor is used to remember the current iterating position.
Expand Down
16 changes: 7 additions & 9 deletions src/future/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ use std::{
/// ```
///
pub struct CacheBuilder<C> {
max_entries: Option<usize>,
// max_weight: Option<usize>,
max_capacity: Option<usize>,
initial_capacity: Option<usize>,
time_to_live: Option<Duration>,
time_to_idle: Option<Duration>,
Expand All @@ -53,8 +52,7 @@ where
{
pub(crate) fn unbound() -> Self {
Self {
max_entries: None,
// max_weight: None,
max_capacity: None,
initial_capacity: None,
time_to_live: None,
time_to_idle: None,
Expand All @@ -67,7 +65,7 @@ where
/// up to `max_capacity` entries.
pub fn new(max_capacity: usize) -> Self {
Self {
max_entries: Some(max_capacity),
max_capacity: Some(max_capacity),
..Self::unbound()
}
}
Expand All @@ -76,7 +74,7 @@ where
pub fn build(self) -> Cache<K, V, RandomState> {
let build_hasher = RandomState::default();
Cache::with_everything(
self.max_entries,
self.max_capacity,
self.initial_capacity,
build_hasher,
self.time_to_live,
Expand All @@ -91,7 +89,7 @@ where
S: BuildHasher + Clone + Send + Sync + 'static,
{
Cache::with_everything(
self.max_entries,
self.max_capacity,
self.initial_capacity,
hasher,
self.time_to_live,
Expand Down Expand Up @@ -158,7 +156,7 @@ mod tests {
// Cache<char, String>
let cache = CacheBuilder::new(100).build();

assert_eq!(cache.max_entries(), Some(100));
assert_eq!(cache.max_capacity(), Some(100));
assert_eq!(cache.time_to_live(), None);
assert_eq!(cache.time_to_idle(), None);
assert_eq!(cache.num_segments(), 1);
Expand All @@ -171,7 +169,7 @@ mod tests {
.time_to_idle(Duration::from_secs(15 * 60))
.build();

assert_eq!(cache.max_entries(), Some(100));
assert_eq!(cache.max_capacity(), Some(100));
assert_eq!(cache.time_to_live(), Some(Duration::from_secs(45 * 60)));
assert_eq!(cache.time_to_idle(), Some(Duration::from_secs(15 * 60)));
assert_eq!(cache.num_segments(), 1);
Expand Down
42 changes: 25 additions & 17 deletions src/future/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,15 @@ where
K: Hash + Eq + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
/// Constructs a new `Cache<K, V>` that will store up to the `max_entries`.
/// Constructs a new `Cache<K, V>` that will store up to the `max_capacity`.
///
/// To adjust various configuration knobs such as `initial_capacity` or
/// `time_to_live`, use the [`CacheBuilder`][builder-struct].
///
/// [builder-struct]: ./struct.CacheBuilder.html
pub fn new(max_entries: usize) -> Self {
pub fn new(max_capacity: usize) -> Self {
let build_hasher = RandomState::default();
Self::with_everything(Some(max_entries), None, build_hasher, None, None, false)
Self::with_everything(Some(max_capacity), None, build_hasher, None, None, false)
}

pub fn builder() -> CacheBuilder<Cache<K, V, RandomState>> {
Expand All @@ -238,7 +238,7 @@ where
S: BuildHasher + Clone + Send + Sync + 'static,
{
pub(crate) fn with_everything(
max_entries: Option<usize>,
max_capacity: Option<usize>,
initial_capacity: Option<usize>,
build_hasher: S,
time_to_live: Option<Duration>,
Expand All @@ -247,7 +247,7 @@ where
) -> Self {
Self {
base: BaseCache::new(
max_entries,
max_capacity,
initial_capacity,
build_hasher.clone(),
time_to_live,
Expand Down Expand Up @@ -417,14 +417,9 @@ where
self.base.invalidate_entries_if(Arc::new(predicate))
}

/// Returns the `max_entries` of this cache.
pub fn max_entries(&self) -> Option<usize> {
self.base.max_entries()
}

/// Returns the `max_weight` of this cache.
pub fn max_weight(&self) -> Option<u64> {
self.base.max_weight()
/// Returns the `max_capacity` of this cache.
pub fn max_capacity(&self) -> Option<usize> {
self.base.max_capacity()
}

/// Returns the `time_to_live` of this cache.
Expand Down Expand Up @@ -510,6 +505,7 @@ where
.await
{
InitResult::Initialized(v) => {
let hash = self.base.hash(&key);
self.insert_with_hash(Arc::clone(&key), hash, v.clone())
.await;
self.value_initializer
Expand Down Expand Up @@ -639,10 +635,16 @@ mod tests {
// counts: a -> 1, b -> 1, c -> 1
cache.sync();

assert_eq!(cache.get(&"a"), Some("alice"));
assert_eq!(cache.get(&"b"), Some("bob"));
assert_eq!(cache.get(&"c"), Some("cindy"));
cache.sync();
// counts: a -> 2, b -> 2, c -> 2

assert_eq!(cache.get(&"a"), Some("alice"));
assert_eq!(cache.get(&"b"), Some("bob"));
cache.sync();
// counts: a -> 2, b -> 2, c -> 1
// counts: a -> 3, b -> 3, c -> 2

// "d" should not be admitted because its frequency is too low.
cache.insert("d", "david").await; // count: d -> 0
Expand All @@ -654,7 +656,7 @@ mod tests {
assert_eq!(cache.get(&"d"), None); // d -> 2

// "d" should be admitted and "c" should be evicted
// because d's frequency is higher then c's.
// because d's frequency equals to c's.
cache.insert("d", "dennis").await;
cache.sync();
assert_eq!(cache.get(&"a"), Some("alice"));
Expand Down Expand Up @@ -686,10 +688,16 @@ mod tests {
// counts: a -> 1, b -> 1, c -> 1
cache.sync();

assert_eq!(cache.get(&"a"), Some("alice"));
assert_eq!(cache.get(&"b"), Some("bob"));
assert_eq!(cache.get(&"c"), Some("cindy"));
cache.sync();
// counts: a -> 2, b -> 2, c -> 2

assert_eq!(cache.get(&"a"), Some("alice"));
assert_eq!(cache.get(&"b"), Some("bob"));
cache.sync();
// counts: a -> 2, b -> 2, c -> 1
// counts: a -> 3, b -> 3, c -> 2

// "d" should not be admitted because its frequency is too low.
cache.blocking_insert("d", "david"); // count: d -> 0
Expand All @@ -701,7 +709,7 @@ mod tests {
assert_eq!(cache.get(&"d"), None); // d -> 2

// "d" should be admitted and "c" should be evicted
// because d's frequency is higher then c's.
// because d's frequency equals to c's.
cache.blocking_insert("d", "dennis");
cache.sync();
assert_eq!(cache.get(&"a"), Some("alice"));
Expand Down
Loading

0 comments on commit 2835b94

Please sign in to comment.