Skip to main content

Crate neocache

Crate neocache 

Source
Expand description

§neocache

A low-latency concurrent hash map with S3-FIFO cache eviction, forked from DashMap 6.1.0.

S3-FIFO is a simple, scalable eviction policy that outperforms LRU in miss-ratio across a wide range of access patterns while being cheaper to implement: it uses only FIFO queues and a small ghost set.

§How it works

NeoCache is a sharded concurrent hash map. Each shard is an RwLock<ShardData> where ShardData holds both the raw hashbrown table and the complete S3-FIFO eviction state for that shard. There is no global eviction lock — eviction is concurrent with the same granularity as reads and writes.

§S3-FIFO algorithm

Each shard maintains three structures:

StructureSizePurpose
Small queue~10% of shard capacityNew entries enter here
Main queue~90% of shard capacityHot entries (second-chance eviction)
Ghost set~100% of shard capacityHashes of recently evicted entries

Insertion: new entries go to the Small queue. If the key was recently evicted (ghost hit), it goes directly to Main instead.

Eviction from Small: if the entry’s frequency counter is > 0, it is promoted to Main (freq ≥ 1 indicates it was accessed at least once). If freq == 0, it is evicted and its hash is added to the ghost set.

Eviction from Main: entries with freq > 0 get a second chance — freq is decremented and the entry is re-enqueued at the back of Main. Entries with freq == 0 are evicted.

Frequency tracking: reads call bump_freq() under the existing read lock, saturating at 3. No extra locking is needed.

§Usage

[dependencies]
neocache = { path = "..." }
use neocache::NeoCache;

// Create a map that evicts entries beyond 10_000 items.
let cache: NeoCache<String, Vec<u8>> = NeoCache::new(10_000);

cache.insert("key".to_string(), vec![1, 2, 3]);

if let Some(v) = cache.get("key") {
    println!("{:?}", *v);
}

cache.remove("key");

§Unbounded map (no eviction)

let map: NeoCache<u64, u64> = NeoCache::new_unbounded();

§Custom shard count

The shard count must be a power of two greater than 1. Choosing a count that evenly divides your capacity gives a tighter bound on the maximum live entries.

// 4 shards, 16 entries per shard, total capacity exactly 64.
let cache = NeoCache::<u64, u64>::with_shard_amount(64, 4);

§Custom hasher

use neocache::NeoCache;
use std::collections::hash_map::RandomState;

let cache: NeoCache<String, u64, RandomState> =
    NeoCache::with_capacity_and_hasher(1_000, RandomState::new());

§Entry API

cache.entry("counter".to_string())
    .and_modify(|v| *v += 1)
    .or_insert(0);

§Iteration

for r in &cache {
    let (k, v) = r.pair();
    println!("{k}: {v:?}");
}

§Design notes

§Keys must be Clone

K: Clone is required for insert and entry(...).or_insert(...) because each key must be stored in two places: the hashbrown table and the eviction queue (as a (hash, K) pair). The clone happens once per insertion.

§Capacity is approximate

The total capacity guarantee is shard_cap * shard_count, where shard_cap = ceil(capacity / shard_count). The default shard count is 16 × logical CPUs (rounded to the next power of two), automatically reduced for small caches so that each shard holds at least 4 entries. With a capacity of N, the map may hold up to N + shard_count - 1 live entries. Use with_shard_amount with a power-of-two shard count that divides your capacity evenly for an exact bound.

§Lazy removal

remove() decrements the live counter for the appropriate queue but does not purge stale entries from the FIFO queues. The eviction sweep skips them via a hashbrown find() liveness check. This avoids the cost of scanning queues on every removal.

§No allocation on read

Reads (get, contains_key, iteration) hold the per-shard read lock and bump the frequency counter atomically. No allocation or queue modification occurs on the read path.

§API surface

NeoCache mirrors the DashMap 6.1.0 API. The main additions are the constructors that accept a cache_capacity argument:

ConstructorDescription
new(capacity)Default shard count, eviction enabled
new_unbounded()Default shard count, no eviction
with_shard_amount(capacity, n)Custom shard count, eviction enabled
with_hasher(hasher)Custom hasher, no eviction
with_capacity_and_hasher(capacity, hasher)Custom hasher, eviction enabled
with_hasher_and_shard_amount(hasher, n)Custom hasher and shard count, no eviction
with_capacity_and_hasher_and_shard_amount(capacity, hasher, n)Full control

All DashMap methods are available: insert, get, get_mut, remove, remove_if, contains_key, entry, try_entry, iter, iter_mut, retain, alter, alter_all, len, is_empty, capacity, clear, shrink_to_fit, try_reserve, into_iter, into_read_only, and the try_get / try_get_mut non-blocking variants.

§Dependencies

CrateRole
hashbrownRaw hash table (with raw feature)
ahashDefault hasher
parking_lot_coreCustom reader-writer lock
crossbeam-utilsCache-line padding for shards
lock_apiLock trait abstractions

§License

MIT

Re-exports§

pub use mapref::entry::Entry;
pub use mapref::entry::OccupiedEntry;
pub use mapref::entry::VacantEntry;
pub use t::Map;

Modules§

iter
Iterator types for NeoCache.
mapref
Map reference types returned by lock-holding accessors.
t
Internal Map trait, keeping the same shape as DashMap’s t.rs.
try_result
Non-blocking lookup result type.

Structs§

NeoCache
A concurrent hash map with S3-FIFO cache eviction.
ReadOnlyView
A read-only view into a NeoCache.
TryReserveError
Error returned by NeoCache::try_reserve when allocation fails.