Skip to content

Cost aware LFU - #595

Open
rs-sac wants to merge 4 commits into
moka-rs:mainfrom
rs-sac:cost-aware-lfu
Open

rs-sac wants to merge 4 commits into
moka-rs:mainfrom
rs-sac:cost-aware-lfu

Conversation

@rs-sac

@rs-sac rs-sac commented Jun 10, 2026

Copy link
Copy Markdown

This series of patches contains a relatively simple variation on the existing LFU algorithm that compares a potential admission to existing cache items based on a weighted cost rather than straight frequency of use. The idea is that if the purpose of the cache is to reduce time spent computing, it may be the case that an item less frequently used is more worth caching if the time required to recompute its value is disproportionately greater.

(Concretely, if item A is used 1x/sec and item B is used 2x/sec, LFU would prefer B. But if A costs 10 to recompute and B costs 1 to recompute, then preferring A would save more computation.)

Thus, this new algorithm compares weighted costs rather than frequencies, where the weighted cost is the frequency multiplied by a cost function (similar to the existing weighing function).


Open in Devin Review

Summary by CodeRabbit

  • New Features

    • Added a Cost-aware TinyLFU eviction policy variant that incorporates per-entry recomputation cost into admission and eviction decisions (frequency × cost).
    • Added a new cost() method to the cache builder to supply per-entry recomputation cost for the cost-aware policy.
  • Tests

    • Added/updated unit tests to validate cost-aware admission/eviction outcomes.
  • Documentation

    • Updated eviction/admission policy documentation to describe the cost-aware behavior.

rs-sac added 2 commits June 9, 2026 15:48
No functional change:

- Move `EntrySizeAndFrequency` from `sync::base_cache` and
  `future::base_cache` (duplicated) to `common::concurrent`
- Add `EvictionPolicyConfig::uses_frequency_sketch` instead of comparing
  against `EvictionPolicyConfig::TinyLfu` directly
- Fix doc wording in `sync::builder` ("to the cache" -> "of the cache")
Add a `cost` closure to the `sync` and `future` cache builders, analogous
to `weigher`, and store the resulting `policy_cost` in `EntryInfo`. The
cost represents the relative cost of recomputing an entry's value and,
unlike the policy weight, does not affect capacity accounting.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bc94cf11-f743-4129-b3f8-f496846689c3

📥 Commits

Reviewing files that changed from the base of the PR and between 7b24c32 and fb63d3c.

📒 Files selected for processing (6)
  • src/common/concurrent.rs
  • src/future/base_cache.rs
  • src/future/cache.rs
  • src/policy.rs
  • src/sync/base_cache.rs
  • src/sync/cache.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/sync/cache.rs
  • src/common/concurrent.rs
  • src/policy.rs
  • src/future/cache.rs
  • src/future/base_cache.rs
  • src/sync/base_cache.rs

📝 Walkthrough

Walkthrough

This PR introduces a cost-aware TinyLFU eviction policy variant that extends cache entries with a separate cost value for fine-grained admission decisions. Entry metadata, policy configuration, both async and sync cache implementations, builder APIs, and comprehensive tests are updated to support cost-weighted frequency aggregation during admission.

Changes

Cost-aware TinyLFU eviction policy

Layer / File(s) Summary
Entry metadata and cost tracking
src/common/concurrent/entry_info.rs, src/common/concurrent.rs
EntryInfo<K> gains a policy_cost: AtomicU32 field alongside policy_weight, with accessor/mutator methods. ValueEntry adds policy_cost() delegation. DEFAULT_COST constant is defined as the fallback cost weight.
Policy configuration and cost aggregation
src/common/concurrent.rs, src/policy.rs
EntrySizeAndFrequency struct introduced to track cumulative policy weight and cost-weighted frequency. EvictionPolicyConfig extended with entry_cost() and add_entry() methods. New CostAwareLfu policy variant and cost_aware_lfu() constructor added to EvictionPolicy. Helper methods updated to recognize cost-aware policies.
Future async BaseCache cost implementation
src/future/base_cache.rs
BaseCache::new and Inner extended with cost: Option<Weigher<K, V>> parameter. Per-entry cost computed during insert/modify and stored in EntryInfo. Inner::cost() helper method added. Admission logic for TinyLfu and CostAwareLfu now aggregates cost-weighted frequency using policy.add_entry().
Sync BaseCache cost implementation
src/sync/base_cache.rs
Mirrors future implementation: constructors accept cost closure, per-entry cost computed and stored during mutations, Inner::cost() helper added, and admission logic delegates cost-weighted victim aggregation to policy.
Future builder and Cache API
src/future/builder.rs, src/future/cache.rs
CacheBuilder gains cost field and public cost() method. build() and build_with_hasher() forward cost to Cache::with_everything(). Cache::with_everything signature extended to accept and forward cost to BaseCache. New cost_aware_single_thread unit test validates behavior.
Sync builder and Cache API
src/sync/builder.rs, src/sync/cache.rs
CacheBuilder adds cost field, public cost() method, and forwards cost through all build paths (including SegmentedCache). Cache::with_everything and SegmentedCache::with_everything signatures extended. New cost_aware_single_thread unit test added.
SegmentedCache cost wiring
src/sync/segment.rs
SegmentedCache and Inner constructors extended to accept cost parameter. Each internal Cache segment constructed with both weigher and cost closures cloned.
Test utilities
src/common/timer_wheel.rs
Timer wheel test imports updated to include DEFAULT_COST. Test helper schedule_timer adjusted to pass DEFAULT_COST to EntryInfo::new().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

In caches fair where eviction flows,
A cost-aware path now grows,
TinyLFU learns to weigh,
Each entry's burden on that day,
With frequency and cost entwined,
Better choices the admission will find! 🦌✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Cost aware LFU' directly and concisely describes the main feature being introduced—a cost-aware variant of the LFU eviction policy. It accurately reflects the primary change across the entire changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.38710% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.45%. Comparing base (d2e6116) to head (fb63d3c).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #595      +/-   ##
==========================================
+ Coverage   93.38%   93.45%   +0.06%     
==========================================
  Files          44       44              
  Lines       17075    17283     +208     
==========================================
+ Hits        15946    16151     +205     
- Misses       1129     1132       +3     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +38 to +42
/// `policy_cost` is the relative cost of recomputing the entry's value (for
/// example, the time it takes to load it). Unlike `policy_weight`, it does not
/// affect the cache's capacity accounting. It is only consulted by the
/// cost-aware eviction policy. Defaults to `1`.
policy_cost: AtomicU32,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Per-entry memory overhead: new AtomicU32 for policy_cost on every entry

Every EntryInfo now contains an additional policy_cost: AtomicU32 field (src/common/concurrent/entry_info.rs:42), adding 4 bytes per cache entry regardless of whether the cost-aware policy is used. For caches with millions of entries, this is a non-trivial memory increase. This is a trade-off that could be mitigated by conditionally including the field (e.g., via a separate entry type or an Option), but the current approach keeps the code simpler.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@ben-manes

Copy link
Copy Markdown

fwiw, there was some research in this area that might be interesting. Sometimes the simple and obvious approach works, sometimes it doesn't. Most of the time, caches are configured and not monitored for tuning so I am always hesitant to give eviction algorithm knobs that may be worse if set. I think its also worthwhile for a cache to emphasize that users would need to monitor for the new goal (from hit rate to request latency) since its less intuitive about what good performance results are.

rs-sac added 2 commits June 22, 2026 11:49
Add `EvictionPolicy::cost_aware_lfu()`, a TinyLFU variant that weights
each entry's sketch frequency by its policy cost (from the builder's
`cost` closure), so that an entry that is accessed less often but is
expensive to recompute can potentially be kept in favor of one that is
accessed more often but is cheap to recompute.

Admission compares the candidate's and victims' `frequency * cost`
instead of frequency alone.  With no `cost` closure configured, every
entry has a cost of 1 and the policy behaves like plain TinyLFU.

Over-capacity eviction is unchanged (LRU order).  (Since the LFU-based
policies rely primarily on admission-time size enforcement, I did not
bother complicating the eviction code.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants