No description
Find a file
Jacek Sieka e3d6e6eb5f
Fix iteration order docs (#7)
The iteration order was wrongly described as LRU when in fact the
iteration happens in MRU order and LRU order is expensive to establish
in the current implementation.

To iterate in the opposite direction (and thus allow popping the least
recently used item when the cache is not full), one would have to keep
explicit track of where that item lives - feasible, but the cost of
keeping it up to date would have to be considered.
2025-12-22 13:03:02 +01:00
.github/workflows Fix iteration order docs (#7) 2025-12-22 13:03:02 +01:00
src Fix iteration order docs (#7) 2025-12-22 13:03:02 +01:00
tests Fix iteration order docs (#7) 2025-12-22 13:03:02 +01:00
.gitignore Add pop operation (#3) 2025-03-01 17:17:29 +07:00
minilru.nimble Initial commit 2024-09-10 16:28:02 +02:00
nim.cfg Initial commit 2024-09-10 16:28:02 +02:00
README.md putWithEvicted for observing the evicted / updated item before it's… (#6) 2025-10-30 15:48:55 +01:00

minilru

Efficient implementation of classic LRU cache with a tightly packed doubly-linked list and robin-hood style hash table.

Usage

In .nimble:

requires "minilru"

In code:

# Create cache that holds up to 2 items
var lru = LruCache[int, int].init(2)

lru.put(10, 10)
lru.put(20, 20)

assert lru.get(10) == Opt.some(10)

lru.put(30, 30)

# 10 was more recent
assert lru.get(20).isNone()

# Allow capacity to grow to 3 items if needed
lru.capacity = 3

# Accessed to evicted 20
for (evicted, key, value) in lru.putWithEvicted(40, 40):
  assert evicted and key == 20

assert lru.get(20).isNone()

Features

  • Low overhead (~18-20 bytes) and no separate allocation per entry
  • Single copy of key and value
  • Heterogenous lookup
    • Different type can be used for lookup as long as == and hash is implemented and equal
  • No exceptions

Implementation notes

The list links, keys and values are stored in a contiguous seq with links being uint32 indices - as a consequence, capacity is capped at 2**32 entries even on 64-bit platforms.

The table similarly maps hashes to indices resulting in a tight packing for the buckets and low memory overhead. Robin-hood open addressing is used for resolving collisions.

Overhead at roughly 18-20 bytes per item in addition to storage for key and value - 8 for the linked list and (8/0.8 + rounding) for hash buckets.

Items are moved to the front on access and add and evicted from the back when full.

The table supports heterogenous lookups, ie using a different key type than is assigned to the table. When doing so, the types must be comparable for both equality (==) and hash (hash).

Robin-hood hashing:

The layout of the LRU node list was inspired by:

Other LRU implementations