# Quick Cache
[![Crates.io](https://img.shields.io/crates/v/quick_cache.svg)](https://crates.io/crates/quick_cache)
[![Docs](https://docs.rs/quick_cache/badge.svg)](https://docs.rs/quick_cache/latest)
[![CI](https://github.com/arthurprs/quick-cache/actions/workflows/ci.yml/badge.svg)](https://github.com/arthurprs/quick-cache/actions/workflows/ci.yml)
Lightweight and high performance concurrent cache optimized for low cache overhead.
* Small overhead compared to a concurrent hash table
* Scan resistent and high hit rate caching policy
* 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
* Supports item pinning
* 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, iterators, 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](https://crates.io/crates/moka) crate.
## Examples
Basic usage
```rust
use quick_cache::unsync::Cache;
fn main() {
let 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.
```rust
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 cautions out 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");
}
```
Using the `Equivalent` trait for complex keys
```rust
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](https://github.com/moka-rs/mokabench) in a x64 Linux OS + Intel i9-12900H CPU.
### Trace 1 (S3 from the Arc paper)
| **QuickCache** | 100000 | 1 | 14300769 | 16407702 | 12.841 | 2.196 |
| **QuickCache** | 100000 | 3 | 14301124 | 16407702 | 12.839 | 1.279 |
| **QuickCache** | 100000 | 6 | 14300809 | 16407702 | 12.841 | 0.798 |
| LRU+Mutex | 100000 | 1 | 16025830 | 16407702 | 2.327 | 2.422 |
| TinyUFO | 100000 | 1 | 15685351 | 16407702 | 4.403 | 11.641 |
| TinyUFO | 100000 | 3 | 15900217 | 16407702 | 3.093 | 8.828 |
| TinyUFO | 100000 | 6 | 15918936 | 16407702 | 2.979 | 8.104 |
| Mini Moka | 100000 | 1 | 14695340 | 16407702 | 10.436 | 9.399 |
| Mini Moka | 100000 | 3 | 14679119 | 16407702 | 10.535 | 8.490 |
| Mini Moka | 100000 | 6 | 14706822 | 16407702 | 10.366 | 8.064 |
| **QuickCache** | 400000 | 1 | 9435745 | 16407702 | 42.492 | 2.537 |
| **QuickCache** | 400000 | 3 | 9437141 | 16407702 | 42.483 | 1.323 |
| **QuickCache** | 400000 | 6 | 9436549 | 16407702 | 42.487 | 0.899 |
| LRU+Mutex | 400000 | 1 | 14432404 | 16407702 | 12.039 | 2.766 |
| TinyUFO | 400000 | 1 | 11455971 | 16407702 | 30.179 | 14.234 |
| TinyUFO | 400000 | 3 | 12405638 | 16407702 | 24.391 | 8.695 |
| TinyUFO | 400000 | 6 | 12551335 | 16407702 | 23.503 | 7.008 |
| Mini Moka | 400000 | 1 | 9427172 | 16407702 | 42.544 | 8.511 |
| Mini Moka | 400000 | 3 | 9584914 | 16407702 | 41.583 | 6.624 |
| Mini Moka | 400000 | 6 | 9656084 | 16407702 | 41.149 | 6.613 |
| **QuickCache** | 800000 | 1 | 5184786 | 16407702 | 68.400 | 3.207 |
| **QuickCache** | 800000 | 3 | 5185210 | 16407702 | 68.398 | 1.353 |
| **QuickCache** | 800000 | 6 | 5185337 | 16407702 | 68.397 | 0.743 |
| LRU+Mutex | 800000 | 1 | 7120978 | 16407702 | 56.600 | 2.613 |
| TinyUFO | 800000 | 1 | 5598215 | 16407702 | 65.881 | 10.043 |
| TinyUFO | 800000 | 3 | 6007956 | 16407702 | 63.383 | 5.077 |
| TinyUFO | 800000 | 6 | 6071806 | 16407702 | 62.994 | 3.789 |
| Mini Moka | 800000 | 1 | 4872358 | 16407702 | 70.304 | 8.574 |
| Mini Moka | 800000 | 3 | 5012764 | 16407702 | 69.449 | 5.363 |
| Mini Moka | 800000 | 6 | 5529311 | 16407702 | 66.301 | 4.551 |
### Trace 2 (DS1 from the Arc paper)
| **QuickCache** | 1000000 | 1 | 37208683 | 43704979 | 14.864 | 9.883 |
| **QuickCache** | 1000000 | 3 | 37196302 | 43704979 | 14.892 | 4.546 |
| **QuickCache** | 1000000 | 6 | 37196402 | 43704979 | 14.892 | 3.585 |
| LRU+Mutex | 1000000 | 1 | 42356290 | 43704979 | 3.086 | 8.901 |
| TinyUFO | 1000000 | 1 | 42093670 | 43704979 | 3.687 | 60.135 |
| TinyUFO | 1000000 | 3 | 42203137 | 43704979 | 3.436 | 35.306 |
| TinyUFO | 1000000 | 6 | 42214889 | 43704979 | 3.409 | 25.440 |
| Mini Moka | 1000000 | 1 | 37188446 | 43704979 | 14.910 | 25.833 |
| Mini Moka | 1000000 | 3 | 37332123 | 43704979 | 14.582 | 23.078 |
| Mini Moka | 1000000 | 6 | 38104018 | 43704979 | 12.815 | 23.571 |
| **QuickCache** | 4000000 | 1 | 24156843 | 43704979 | 44.727 | 9.497 |
| **QuickCache** | 4000000 | 3 | 24159591 | 43704979 | 44.721 | 4.257 |
| **QuickCache** | 4000000 | 6 | 24204044 | 43704979 | 44.619 | 2.777 |
| LRU+Mutex | 4000000 | 1 | 34856997 | 43704979 | 20.245 | 10.735 |
| TinyUFO | 4000000 | 1 | 32607709 | 43704979 | 25.391 | 49.380 |
| TinyUFO | 4000000 | 3 | 32810383 | 43704979 | 24.928 | 26.440 |
| TinyUFO | 4000000 | 6 | 32840752 | 43704979 | 24.858 | 20.390 |
| Mini Moka | 4000000 | 1 | 23863892 | 43704979 | 45.398 | 26.103 |
| Mini Moka | 4000000 | 3 | 23865870 | 43704979 | 45.393 | 19.896 |
| Mini Moka | 4000000 | 6 | 25152629 | 43704979 | 42.449 | 17.912 |
| **QuickCache** | 8000000 | 1 | 13405437 | 43704979 | 69.327 | 7.703 |
| **QuickCache** | 8000000 | 3 | 13406862 | 43704979 | 69.324 | 3.598 |
| **QuickCache** | 8000000 | 6 | 13406780 | 43704979 | 69.324 | 2.243 |
| LRU+Mutex | 8000000 | 1 | 24896662 | 43704979 | 43.035 | 8.995 |
| TinyUFO | 8000000 | 1 | 20143204 | 43704979 | 53.911 | 28.245 |
| TinyUFO | 8000000 | 3 | 20339486 | 43704979 | 53.462 | 14.967 |
| TinyUFO | 8000000 | 6 | 20364270 | 43704979 | 53.405 | 11.206 |
| Mini Moka | 8000000 | 1 | 14227354 | 43704979 | 67.447 | 29.082 |
| Mini Moka | 8000000 | 3 | 13762562 | 43704979 | 68.510 | 17.060 |
| Mini Moka | 8000000 | 6 | 14926093 | 43704979 | 65.848 | 12.294 |
### Trace 3 (SPC1 from the Arc paper)
| **QuickCache** | 500000 | 1 | 36994843 | 41351279 | 10.535 | 7.880 |
| LRU+Mutex | 500000 | 1 | 39942249 | 41351279 | 3.407 | 9.820 |
| TinyUFO | 500000 | 1 | 39197208 | 41351279 | 5.209 | 49.395 |
| Mini Moka | 500000 | 1 | 36231669 | 41351279 | 12.381 | 25.467 |
| **QuickCache** | 2000000 | 1 | 24123918 | 41351279 | 41.661 | 14.611 |
| LRU+Mutex | 2000000 | 1 | 30181071 | 41351279 | 27.013 | 10.768 |
| TinyUFO | 2000000 | 1 | 27173449 | 41351279 | 34.286 | 46.476 |
| Mini Moka | 2000000 | 1 | 24032624 | 41351279 | 41.882 | 31.064 |
| **QuickCache** | 30000000 | 1 | 6050363 | 41351279 | 85.368 | 8.170 |
| LRU+Mutex | 30000000 | 1 | 6050363 | 41351279 | 85.368 | 8.594 |
| TinyUFO | 30000000 | 1 | 6050363 | 41351279 | 85.368 | 14.065 |
| Mini Moka | 30000000 | 1 | 6050363 | 41351279 | 85.368 | 34.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](https://github.com/arthurprs/quick-cache/issues/49#issuecomment-2323322103)
## References
* [CLOCK-Pro: An Effective Improvement of the CLOCK Replacement (pdf)](https://www.usenix.org/legacy/events/usenix05/tech/general/full_papers/jiang/jiang.pdf)
* [FIFO queues are all you need for cache eviction (pdf)](https://dl.acm.org/doi/10.1145/3600006.3613147)
* [Caffeine (source code)](https://github.com/ben-manes/caffeine)
* [Cache2k (source code)](https://github.com/cache2k/cache2k)
## License
This project is licensed under the MIT license.