LRU-cap resolved local type aliases in UsefulTypeAliasResolver#5989
Closed
ondrejmirtes wants to merge 2 commits into
Closed
LRU-cap resolved local type aliases in UsefulTypeAliasResolver#5989ondrejmirtes wants to merge 2 commits into
ondrejmirtes wants to merge 2 commits into
Conversation
Every resolved @phpstan-type local alias is cached forever, keyed by Class::alias. Resolved alias Types transitively pin ClassReflections (ObjectType::$classReflection, EnumCaseObjectType::$classReflection, ...) and through them whole BetterReflection trees, so on alias-heavy codebases this cache quietly retains a large share of the reflection universe. Retention-path instrumentation on a large project identified it as the single largest type-level pin: bounding it freed 34 MB end-of-run memory at 2,358 analysed files — more than any reflection-side cache — with byte-identical error output and no measurable time cost (interleaved A/B, 100 s vs 95 s). Cap the cache at 2048 entries with least-recently-used eviction; evicted aliases are re-resolved on demand. Global aliases are unaffected (bounded by config). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrTvR9j1mW2NNNC8RkuTsF
b4348df to
e674a79
Compare
Default 2048, 0 disables eviction. Chosen from a cap sweep on a 2,358-file project (all LRU caps set to the sweep value, error output identical at every point): memory is U-shaped in the cap — 936.5 MB at 512, 880.5 MB at 2048, 930.5 MB uncapped — because too-small caps recreate evicted object graphs as duplicates while the old ones are still pinned by sibling caches, and too-large caps converge to unbounded retention. Elapsed time shows no trend across the whole range (84-101 s noise band). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrTvR9j1mW2NNNC8RkuTsF
e674a79 to
d7a49e9
Compare
Member
Author
|
Combined into #5988 per review preference. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
UsefulTypeAliasResolver::$resolvedLocalTypeAliasescaches every resolved@phpstan-typelocal alias forever, keyed byClass::alias. Resolved alias Types transitively pinClassReflections (ObjectType::$classReflection,EnumCaseObjectType::$classReflection, …) and through them whole BetterReflection trees.Retention-path instrumentation on the ShipMonk backend named this cache as the single largest type-level pin of the reflection universe — a sampled path:
Types are stronger pins than reflections themselves: bounding this one cache freed more memory than bounding any reflection-side cache in the same experiment series.
Change
Cap
$resolvedLocalTypeAliasesat 2048 entries with LRU eviction (move-to-end on hit, evict-first on insert). Evicted aliases are re-resolved on demand — pure recomputation. Global aliases are untouched (bounded by config).Measured (2,358-file project, single process)
The 2048 cap could be promoted to a
cache.*parameter if desired.Tests:
LocalTypeAliasesRuleTest+ type-alias NSRT assertions pass; phpcs clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01NrTvR9j1mW2NNNC8RkuTsF
Configurability + default (added)
The cap is now a
cache.*parameter (cache.resolvedLocalTypeAliasesCountMax, default 2048,0disables eviction), following thephpStormStubsNodesCountMaxpattern.The default comes from a cap sweep on the 2,358-file project (all LRU caps set to the sweep value, single process, error output identical — 776 — at every point):
The curve is U-shaped: too-small caps are actively harmful — evicted entries are recreated as fresh object graphs while the old copies are still pinned by sibling caches, so duplicates accumulate (this also explains why very small caps regressed memory on large projects before). Too-large caps converge to unbounded retention. Elapsed time shows no trend across the whole range — the knob trades memory only, so 2048 is chosen purely on the memory optimum.