Answer invalidation checks from a per-holder index of contained node keys - #6152
Answer invalidation checks from a per-holder index of contained node keys#6152ondrejmirtes wants to merge 2 commits into
Conversation
…keys ScopeOps::shouldInvalidateExpression() re-scanned the stored holder's whole subtree on every invalidation check (after the substring and compositional-key pre-filters). The scan establishes a property of the holder's expression alone, so ExpressionTypeHolder now lazily indexes the node keys of its sub-expressions once - holders are shared across scope copies, so the single subtree scan amortizes over every later check - and the check becomes one array lookup, exact by construction. '$this' invalidations keep the finder: they also match self/static/ parent and the current class name, and that name resolution depends on the asking scope. The native twins mirror the same split: the ScopeOps core reads the holder's cached index (built with the native node-key builder) and the ExpressionTypeHolder twin exposes the same lazily-cached getContainedNodeKeys(), key-order identical to the PHP implementation. Ported from the resolve-type-rewrite-2 branch, keeping 2.2.x's newer pre-filter gates (the requireMoreCharacters variable refinement and keyMayHideSubExpressions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
| /Users/ondrej/Development/phpstan/.claude/worktrees/scopeops-invalidation/turbo-ext/src/support.cpp \ | ||
| /Users/ondrej/Development/phpstan/.claude/worktrees/scopeops-invalidation/turbo-ext/src/support.h \ | ||
| /opt/homebrew/Cellar/php/8.5.5_1/include/php/main/php.h \ |
There was a problem hiding this comment.
are these developer machine local path expected ?
There was a problem hiding this comment.
Probably shouldn't be commited at all
With the compositional-key shortcut clearing virtual-node keys, only 308k invalidation checks reach the containment step on a src/Analyser+src/Rules run (down from 1.16M) - the population where the index was measured worthless on 2.2.x (phpstan-src PR #6152, 12 interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps containment (containsExpressionToInvalidate) and their native twins return to the 2.2.x shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
|
For the record: the branch no longer needs this either. The root cause of its 3.2× invalidation-check volume was virtual-node keys defeating the compositional-substring shortcut (plus a missing caller-level prefilter in its invalidateExpressionEntries), not intrinsic load — same events, same holder population as 2.2.x. With the compositional-gate fixes landed there (f65dfce), calls dropped 2.41M → 420k and containment checks 1.16M → 308k — below 2.2.x's 629k, where this PR measured dead neutral — so the index was dropped from the branch too (5ee84cd) and ExpressionTypeHolder/ScopeOps converged back to the 2.2.x shapes. |
With the compositional-key shortcut clearing virtual-node keys, only 308k invalidation checks reach the containment step on a src/Analyser+src/Rules run (down from 1.16M) - the population where the index was measured worthless on 2.2.x (phpstan-src PR #6152, 12 interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps containment (containsExpressionToInvalidate) and their native twins return to the 2.2.x shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
With the compositional-key shortcut clearing virtual-node keys, only 308k invalidation checks reach the containment step on a src/Analyser+src/Rules run (down from 1.16M) - the population where the index was measured worthless on 2.2.x (phpstan-src PR #6152, 12 interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps containment (containsExpressionToInvalidate) and their native twins return to the 2.2.x shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
With the compositional-key shortcut clearing virtual-node keys, only 308k invalidation checks reach the containment step on a src/Analyser+src/Rules run (down from 1.16M) - the population where the index was measured worthless on 2.2.x (phpstan-src PR #6152, 12 interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps containment (containsExpressionToInvalidate) and their native twins return to the 2.2.x shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
With the compositional-key shortcut clearing virtual-node keys, only 308k invalidation checks reach the containment step on a src/Analyser+src/Rules run (down from 1.16M) - the population where the index was measured worthless on 2.2.x (phpstan-src PR #6152, 12 interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps containment (containsExpressionToInvalidate) and their native twins return to the 2.2.x shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
Ported from the resolve-type-rewrite-2 branch.
ScopeOps::shouldInvalidateExpression()re-scanned the stored holder's whole subtree (after the substring/compositional-key pre-filters) on every invalidation check. The scan establishes a property of the holder's expression alone, soExpressionTypeHoldernow lazily indexes the node keys of its sub-expressions once — holders are shared across scope copies, so the single scan amortizes — and the check becomes one array lookup, exact by construction.$thisinvalidations keep the finder (they also match self/static/parent and the current class name, whose resolution depends on the asking scope). 2.2.x's newer pre-filter gates (therequireMoreCharactersvariable refinement,keyMayHideSubExpressions) are kept.The native twins mirror the same split; the
ExpressionTypeHoldertwin exposes the same lazily-cachedgetContainedNodeKeys(), key-order identical.Validation: strict build, smoke (incl. new differential coverage for the index + memoization),
side-by-side.php,signature-parity.php, full test suite green with and without the active extension, byte-identical analysis output oversrc/Analyser src/Ruleswith the extension on vs off.Honest benchmark verdict: neutral on 2.2.x — 12 interleaved ABBA pairs over
src/Analyser src/Rules(no turbo): A=51.54s vs B=51.54s user CPU (±0.03% in both independent rounds). 2.2.x's pre-filters already absorb almost all checks before the subtree scan, so the index has nothing left to win here. The value is convergence: the resolve-type-rewrite-2 branch measured −2.5% user CPU from this change (its engine performs ~2.3M invalidation checks per self-analysis run), carries it as a twin-divergence, and every rebase pays for the conflict surface. If neutral-CPU-for-convergence isn't worth it, holding this until the branch merge is a fine alternative.🤖 Generated with Claude Code
https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7