You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Evict from in-memory caches instead of freezing at the memory ceiling
Once resident memory reached `maximum_cache_memory_bytes` (or
`maximum_cache_memory_percentage`), a cache refused every new entry for the
rest of the process, so which results stayed cached was decided by whichever
happened to be computed first and every later lookup missed. Entries now live
in a `ByteBoundedStore`, shared by `ContentCache` and the module-level `@cache`
decorator, which holds its byte weight at the level it had reached and admits
new entries by evicting least recently used ones.
Occupancy is measured in bytes rather than entries because neither argument
space is bounded by a count: the combinatorial index tables key on a sequence
length alone, giving one entry per system size but values growing as 2^N or
3^N, while `max_entropy_distribution` keys on a purview, giving one entry per
subset. An entry too large to fit the whole budget is refused rather than
allowed to flush the working set, and a bound latched during a transient spike
is re-checked and lifted when memory frees up.
Eviction does not lower resident memory — freeing Python objects returns their
memory to the process allocator, rarely to the operating system — so the policy
holds occupancy steady rather than trying to shrink it. What it changes is
which entries a fixed allocation is spent on. Measured on a scoped
cause-effect structure sweep with the ceiling binding over the last 58% of the
work: the ceiling cost 1.455x the unbounded run under the freeze and 1.037x
under eviction, with the hit rate rising from 72.6% to 95.5% against an
unbounded 95.6%, on fewer entries and lower peak memory than freezing used.
`memory_full()` built a fresh `psutil.Process` on every call, ten times the
cost of reading from an existing one, and it runs on every cache miss: 19s of
a 208s run at 1.3M misses. The handle is now reused and rebuilt after a fork.
The `@cache` decorator's `maxmem` branch is removed: every call site passed
`maxmem=None` and its default bound the config at import time, so it never ran.
Its `cache=` backing-store parameter goes too, since every call site passed a
fresh dict and no store was ever shared. `pyphi.cache.info()` now reports
`nbytes` and `evictions` alongside hits, misses, and entry count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qi3MYsc3afQMxoWZZo2tEL
0 commit comments