Source code for mtlearn.layers.cfp.storage.memory_store

"""Explicit CPU preparation cache with a byte-bounded LRU."""
from collections import OrderedDict

from .null_store import NullStore
from ._storages import storages
from ..preparation.prepared_morphology import PreparedMorphology


[docs] class MemoryStore(NullStore): """Retain unique CPU storages up to ``max_bytes`` (not a process RSS limit). Entries larger than the budget are returned without admission. Eviction drops only the store's reference; active batches and their backward remain valid. Keys supplied directly to this low-level store must identify immutable content. CFPPreprocessor calculates keys from canonical pixels and preparation options. """ retains_entries = True def __init__(self, max_bytes): if type(max_bytes) is not int or max_bytes < 0: raise ValueError("max_bytes must be a non-negative integer.") super().__init__() self._max_bytes = max_bytes self._entries = OrderedDict() self._storage_counts = {} self._retained_bytes = 0 @property def max_bytes(self): return self._max_bytes def get(self, key): if key not in self._entries: self._misses += 1 return None self._hits += 1 self._entries.move_to_end(key) return self._borrow(self._entries[key]) def _remove(self, key): prepared = self._entries.pop(key) for pointer in storages(prepared): size, count = self._storage_counts[pointer] if count == 1: del self._storage_counts[pointer] self._retained_bytes -= size else: self._storage_counts[pointer] = size, count - 1
[docs] def put(self, key, prepared): if not isinstance(prepared, PreparedMorphology): raise TypeError("Stores accept PreparedMorphology objects only.") prepared.validate() hash(key) # Fail before changing retention if the key is not hashable. buffers = storages(prepared) if key in self._entries: self._remove(key) if sum(buffers.values()) > self.max_bytes: self._oversized += 1 return self._borrow(prepared) while self._retained_bytes + sum(size for pointer, size in buffers.items() if pointer not in self._storage_counts) > self.max_bytes: self._remove(next(iter(self._entries))) self._evictions += 1 # Never store the consumer wrapper itself: its lifetime measures use. from dataclasses import replace self._entries[key] = replace(prepared) for pointer, size in buffers.items(): _, count = self._storage_counts.get(pointer, (size, 0)) self._storage_counts[pointer] = size, count + 1 if count == 0: self._retained_bytes += size return self._borrow(prepared)
[docs] def clear(self): self._entries.clear() self._storage_counts.clear() self._retained_bytes = 0
def info(self): resident = {key: value[0] for key, value in self._storage_counts.items()} return { "max_bytes": self.max_bytes, "retained_bytes": self._retained_bytes, "entries": len(self._entries), "hits": self._hits, "misses": self._misses, "evictions": self._evictions, "oversized_entries": self._oversized, **self._consumer_info(resident), }