Quick Cache

May 12, 2026 ยท View on GitHub

Crates.io Docs CI

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 methods that return immediately on lock contention.
  • 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;

fn main() {
    let mut 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)]
struct StringWeighter;

impl Weighter<u64, String> for StringWeighter {
    fn weight(&self, _key: &u64, val: &String) -> u64 {
        // Be cautious about zero weights!
        val.len() as u64
    }
}

fn main() {
    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};

fn main() {
    let cache: Cache<u64, u64> = Cache::new(100);

    // Insert-or-get: if absent, compute and insert; if present, return cached
    let 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 threshold
    let 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)]
pub struct Pair<A, B>(pub A, pub B);

impl<A, B, C, D> Equivalent<(C, D)> for Pair<A, B>
where
    A: PartialEq<C>,
    B: PartialEq<D>,
{
    fn equivalent(&self, rhs: &(C, D)) -> bool {
        self.0 == rhs.0 && self.1 == rhs.1
    }
}

fn main() {
    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.