Files
2026-07-13 12:24:33 +08:00

125 lines
3.8 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""
LRU cache implementations for the cache simulator.
Two variants are provided:
* ``LRUCacheFast`` — O(1) OrderedDict-backed. Supports only hit/miss queries.
Use this for capacity sweeps where per-chunk statistics are not needed.
* ``LRUCache`` — O(log n) dict + SortedList. Adds ``position(key)`` for
computing cache-position statistics (0 = MRU, capacity-1 = LRU).
Use this for the detailed single-run report.
"""
# Standard
from collections import OrderedDict
from typing import Any
# Third Party
from sortedcontainers import SortedList
class LRUCacheFast:
"""
Lightweight LRU cache using a single :class:`OrderedDict`.
All operations are O(1). No per-key position tracking.
Parameters
----------
capacity:
Maximum number of entries before LRU eviction kicks in.
"""
def __init__(self, capacity: int) -> None:
if capacity < 1:
raise ValueError(f"LRUCacheFast capacity must be >= 1, got {capacity}")
self.capacity = capacity
self._cache: OrderedDict[Any, None] = OrderedDict()
self.eviction_count: int = 0
def contains(self, key: Any) -> bool:
return key in self._cache
def access(self, key: Any) -> None:
"""Mark an existing entry as most-recently used. O(1)."""
self._cache.move_to_end(key)
def insert(self, key: Any) -> None:
"""Insert a new entry, or refresh if already present. O(1)."""
if key in self._cache:
self._cache.move_to_end(key)
return
if len(self._cache) >= self.capacity:
self._cache.popitem(last=False)
self.eviction_count += 1
self._cache[key] = None
def __len__(self) -> int:
return len(self._cache)
class LRUCache:
"""
LRU cache backed by a dict (O(1) lookup) and a
:class:`~sortedcontainers.SortedList` (O(log n) rank queries).
Each entry carries a strictly-increasing clock value so that the SortedList
order is unambiguous.
* LRU end = smallest clock value = ``SortedList[0]``
* MRU end = largest clock value = ``SortedList[-1]``
Parameters
----------
capacity:
Maximum number of entries before LRU eviction kicks in.
"""
def __init__(self, capacity: int) -> None:
if capacity < 1:
raise ValueError(f"LRUCache capacity must be >= 1, got {capacity}")
self.capacity = capacity
self._clock: int = 0
self._map: dict[Any, int] = {} # key -> clock value at last access
self._sl: SortedList = SortedList(key=lambda x: x[0])
self.eviction_count: int = 0
def contains(self, key: Any) -> bool:
return key in self._map
def position(self, key: Any) -> int:
"""
LRU rank of *key*: 0 = most-recently used, ``len-1`` = least-recently used.
O(log n).
"""
clock = self._map[key]
idx = self._sl.index((clock, key))
return len(self._sl) - 1 - idx
def access(self, key: Any) -> None:
"""Mark an existing entry as most-recently used. O(log n)."""
old_clock = self._map[key]
self._sl.remove((old_clock, key))
self._clock += 1
self._map[key] = self._clock
self._sl.add((self._clock, key))
def insert(self, key: Any) -> None:
"""Insert a new entry, or refresh if already present. O(log n)."""
if key in self._map:
self.access(key)
return
if len(self._map) >= self.capacity:
lru_clock, lru_key = self._sl[0]
self._sl.remove((lru_clock, lru_key))
del self._map[lru_key]
self.eviction_count += 1
self._clock += 1
self._map[key] = self._clock
self._sl.add((self._clock, key))
def __len__(self) -> int:
return len(self._map)