Skip to content

Repository files navigation

Quick Cache

Crates.ioDocsCI

Lightweight and high performance concurrent cache optimized for low cache overhead.

  • Small overhead compared to a concurrent hash table
  • Scan resistant and high hit rate caching policy (S3-FIFO)
  • User defined weight per item
  • Scales well with the number of threads
  • Atomic operations with get_or_insert and get_value_or_guard functions
  • Atomic async operations with get_or_insert_async and get_value_or_guard_async functions
  • Non-blocking try_get, try_insert, try_remove, and related methods that return an error instead of blocking: typically Err(LockContention), or Err((Key, Val)) for try_insert/try_insert_with_lifecycle so inputs are preserved
  • Closure-based entry API for atomic inspect-and-act patterns (keep, remove, replace)
  • Supports item pinning
  • Iteration and draining
  • Handles zero weight items efficiently
  • Allows for customizable lifecycle hooks (e.g. can be used to implement eviction listeners)
  • Doesn't use background threads
  • Only trivially verifiable usages of unsafe
  • Small dependency tree

The implementation is optimized for use cases where the cache access times and overhead can add up to be a significant cost. Features like: time to live, event listeners and others; are partially or not implemented in Quick Cache. If you need these features you may want to take a look at the Moka crate.

Examples

Basic usage

use quick_cache::unsync::Cache;fnmain(){letmut cache = Cache::new(5);
cache.insert("square","blue");
cache.insert("circle","black");assert_eq!(*cache.get(&"square").unwrap(),"blue");assert_eq!(*cache.get(&"circle").unwrap(),"black");}

A cache with custom item weights. In this case according to the string length of the value.

use quick_cache::{Weighter, sync::Cache};#[derive(Clone)]structStringWeighter;implWeighter<u64,String>forStringWeighter{fnweight(&self,_key:&u64,val:&String) -> u64{// Be cautious about zero weights!
val.len()asu64}}fnmain(){let cache = Cache::with_weighter(100,100_000,StringWeighter);
cache.insert(1,"1".to_string());
cache.insert(54,"54".to_string());
cache.insert(1000,"1000".to_string());assert_eq!(cache.get(&1000).unwrap(),"1000");}

Atomic inspect-and-act with the entry API

use quick_cache::sync::{Cache,EntryAction,EntryResult};fnmain(){let cache:Cache<u64,u64> = Cache::new(100);// Insert-or-get: if absent, compute and insert; if present, return cachedlet result = cache.entry(&0,None, |_key, val| EntryAction::Retain(*val));let value = match result {EntryResult::Retained(v) => v,EntryResult::Vacant(guard) => {let v = 42;// expensive computation
guard.insert(v).unwrap();
v
}
_ => unreachable!(),};assert_eq!(value,42);// Conditionally remove: evict entries below a thresholdlet result = cache.entry(&0,None, |_key, val| {if*val < 100{EntryAction::<()>::Remove}else{EntryAction::Retain(())}});assert!(matches!(result,EntryResult::Removed(0,42)));}

Using the Equivalent trait for complex keys

use quick_cache::{sync::Cache,Equivalent};#[derive(Debug,Hash)]pubstructPair<A,B>(pubA,pubB);impl<A,B,C,D>Equivalent<(C,D)>forPair<A,B>whereA:PartialEq<C>,B:PartialEq<D>,{fnequivalent(&self,rhs:&(C,D)) -> bool{self.0 == rhs.0 && self.1 == rhs.1}}fnmain(){let cache:Cache<(String,i32),String> = Cache::new(5);
cache.insert(("square".to_string(),2022),"blue".to_string());
cache.insert(("square".to_string(),2023),"black".to_string());assert_eq!(cache.get(&Pair("square",2022)).unwrap(),"blue");}

Benchmarks

Since this crate is performance oriented it needs some comparisons. That said, benchmarks can be misleading so take everything with a pinch of salt.

Benchmarks performed with mokabench in a x64 Linux OS + Intel i9-12900H CPU.

Trace 1 (S3 from the Arc paper)

CacheMax CapacityClientsInsertsReadsHit RatioDuration Secs
QuickCache1000001143007691640770212.8412.196
QuickCache1000003143011241640770212.8391.279
QuickCache1000006143008091640770212.8410.798
LRU+Mutex100000116025830164077022.3272.422
TinyUFO100000115685351164077024.40311.641
TinyUFO100000315900217164077023.0938.828
TinyUFO100000615918936164077022.9798.104
Mini Moka1000001146953401640770210.4369.399
Mini Moka1000003146791191640770210.5358.490
Mini Moka1000006147068221640770210.3668.064
QuickCache400000194357451640770242.4922.537
QuickCache400000394371411640770242.4831.323
QuickCache400000694365491640770242.4870.899
LRU+Mutex4000001144324041640770212.0392.766
TinyUFO4000001114559711640770230.17914.234
TinyUFO4000003124056381640770224.3918.695
TinyUFO4000006125513351640770223.5037.008
Mini Moka400000194271721640770242.5448.511
Mini Moka400000395849141640770241.5836.624
Mini Moka400000696560841640770241.1496.613
QuickCache800000151847861640770268.4003.207
QuickCache800000351852101640770268.3981.353
QuickCache800000651853371640770268.3970.743
LRU+Mutex800000171209781640770256.6002.613
TinyUFO800000155982151640770265.88110.043
TinyUFO800000360079561640770263.3835.077
TinyUFO800000660718061640770262.9943.789
Mini Moka800000148723581640770270.3048.574
Mini Moka800000350127641640770269.4495.363
Mini Moka800000655293111640770266.3014.551

Trace 2 (DS1 from the Arc paper)

CacheMax CapacityClientsInsertsReadsHit RatioDuration Secs
QuickCache10000001372086834370497914.8649.883
QuickCache10000003371963024370497914.8924.546
QuickCache10000006371964024370497914.8923.585
LRU+Mutex1000000142356290437049793.0868.901
TinyUFO1000000142093670437049793.68760.135
TinyUFO1000000342203137437049793.43635.306
TinyUFO1000000642214889437049793.40925.440
Mini Moka10000001371884464370497914.91025.833
Mini Moka10000003373321234370497914.58223.078
Mini Moka10000006381040184370497912.81523.571
QuickCache40000001241568434370497944.7279.497
QuickCache40000003241595914370497944.7214.257
QuickCache40000006242040444370497944.6192.777
LRU+Mutex40000001348569974370497920.24510.735
TinyUFO40000001326077094370497925.39149.380
TinyUFO40000003328103834370497924.92826.440
TinyUFO40000006328407524370497924.85820.390
Mini Moka40000001238638924370497945.39826.103
Mini Moka40000003238658704370497945.39319.896
Mini Moka40000006251526294370497942.44917.912
QuickCache80000001134054374370497969.3277.703
QuickCache80000003134068624370497969.3243.598
QuickCache80000006134067804370497969.3242.243
LRU+Mutex80000001248966624370497943.0358.995
TinyUFO80000001201432044370497953.91128.245
TinyUFO80000003203394864370497953.46214.967
TinyUFO80000006203642704370497953.40511.206
Mini Moka80000001142273544370497967.44729.082
Mini Moka80000003137625624370497968.51017.060
Mini Moka80000006149260934370497965.84812.294

Trace 3 (SPC1 from the Arc paper)

CacheMax CapacityClientsInsertsReadsHit RatioDuration Secs
QuickCache5000001369948434135127910.5357.880
LRU+Mutex500000139942249413512793.4079.820
TinyUFO500000139197208413512795.20949.395
Mini Moka5000001362316694135127912.38125.467
QuickCache20000001241239184135127941.66114.611
LRU+Mutex20000001301810714135127927.01310.768
TinyUFO20000001271734494135127934.28646.476
Mini Moka20000001240326244135127941.88231.064
QuickCache30000000160503634135127985.3688.170
LRU+Mutex30000000160503634135127985.3688.594
TinyUFO30000000160503634135127985.36814.065
Mini Moka30000000160503634135127985.36834.452

Notes:

  • LRU+Mutex: hashlink crate + std::sync::Mutex
  • SPC1 only includes 1 client to save space, it follows the same trend as other traces.
  • Other Moka variants not included to save space, they perform similarly or worse than Mini Moka. Full results

References

License

This project is licensed under the MIT license.

About

Lightweight and high performance concurrent cache

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages