Skip to content

Memoize ExprHandler dispatch in MutatingScope::resolveType()#6040

Merged
staabm merged 9 commits into
phpstan:2.2.xfrom
staabm:reg
Jul 12, 2026
Merged

Memoize ExprHandler dispatch in MutatingScope::resolveType()#6040
staabm merged 9 commits into
phpstan:2.2.xfrom
staabm:reg

Conversation

@staabm

@staabm staabm commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

resolveType() re-scanned every tagged handler with a linear supports() sweep on each call - 2.3M supports() calls for 56K dispatches measured on a small self-analysis slice. NodeScopeResolver and TypeSpecifier already memoized this dispatch by Expr class in private copies; extract the memo into a shared ExprHandlerRegistry and use it in all three places.

before this PR:

➜  phpstan-src git:(2.2.x) php bin/phpstan clear-result-cache -q && time php -d memory_limit=450M bin/phpstan analyze src/Analyser -q
php -d memory_limit=450M bin/phpstan analyze src/Analyser -q  
22.66s user 1.52s system 320% cpu 7.554 total
➜  phpstan-src git:(2.2.x) php bin/phpstan clear-result-cache -q && time php -d memory_limit=450M bin/phpstan analyze src/Analyser -q
php -d memory_limit=450M bin/phpstan analyze src/Analyser -q  
22.62s user 1.53s system 318% cpu 7.573 total
➜  phpstan-src git:(2.2.x) php bin/phpstan clear-result-cache -q && time php -d memory_limit=450M bin/phpstan analyze src/Analyser -q
php -d memory_limit=450M bin/phpstan analyze src/Analyser -q  
22.63s user 1.49s system 319% cpu 7.548 total

after this PR:

➜  phpstan-src git:(reg) php bin/phpstan clear-result-cache -q && time php -d memory_limit=450M bin/phpstan analyze src/Analyser -q
php -d memory_limit=450M bin/phpstan analyze src/Analyser -q  
21.85s user 1.48s system 322% cpu 7.227 total
➜  phpstan-src git:(reg) php bin/phpstan clear-result-cache -q && time php -d memory_limit=450M bin/phpstan analyze src/Analyser -q
php -d memory_limit=450M bin/phpstan analyze src/Analyser -q  
21.87s user 1.50s system 322% cpu 7.237 total
➜  phpstan-src git:(reg) php bin/phpstan clear-result-cache -q && time php -d memory_limit=450M bin/phpstan analyze src/Analyser -q
php -d memory_limit=450M bin/phpstan analyze src/Analyser -q  
21.83s user 1.45s system 321% cpu 7.236 total

resolveType() re-scanned every tagged handler with a linear supports()
sweep on each call - 2.3M supports() calls for 56K dispatches measured
on a small self-analysis slice. NodeScopeResolver and TypeSpecifier
already memoized this dispatch by Expr class in private copies; extract
the memo into a shared ExprHandlerRegistry and use it in all three
places.
staabm referenced this pull request Jul 11, 2026
resolveType() re-scanned every tagged handler with a linear supports()
sweep on each call - 2.3M supports() calls for 56K dispatches measured
on a small self-analysis slice. NodeScopeResolver and TypeSpecifier
already memoized this dispatch by Expr class in private copies; extract
the memo into a shared ExprHandlerRegistry and use it in all three
places.
@staabm staabm requested a review from VincentLanglet July 11, 2026 21:27
@ondrejmirtes

Copy link
Copy Markdown
Member

I have a different version in the turbo-ext PR. Please take a look (it uses WeakMap) as I want to merge it soon.

If yours performed better it could be merged first but otherwise I'd prefer the other one.

@staabm

staabm commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

best of 3 runs:

no weakmap

➜  phpstan-src git:(reg) hyperfine --prepare 'php bin/phpstan clear-result-cache -q' --runs 5 'php -d memory_limit=450M bin/phpstan analyze src/Analyser -q' -i
Benchmark 1: php -d memory_limit=450M bin/phpstan analyze src/Analyser -q
  Time (mean ± σ):      7.587 s ±  0.040 s    [User: 21.813 s, System: 1.547 s]
  Range (min … max):    7.555 s …  7.652 s    5 runs

with weakmap

➜  phpstan-src git:(reg) ✗ hyperfine --prepare 'php bin/phpstan clear-result-cache -q' --runs 5 'php -d memory_limit=450M bin/phpstan analyze src/Analyser -q' -i
Benchmark 1: php -d memory_limit=450M bin/phpstan analyze src/Analyser -q
  Time (mean ± σ):      7.604 s ±  0.056 s    [User: 21.835 s, System: 1.577 s]
  Range (min … max):    7.545 s …  7.668 s    5 runs

I don't think the weakmap makes a consistent meaningful difference in my benchmark.

I run my benchmark only on src/Analyser atm, because otherwise because of thermal throttling numbers looks screwed.


in case we are fine in not using weakmap, we should revert the last commit in this PR before merging

Comment on lines +29 to +35
$cacheKey = get_class($expr);
if ($expr instanceof Expr\CallLike) {
$cacheKey .= '|' . $expr->isFirstClassCallable();
if ($expr instanceof Expr\New_) {
$cacheKey .= '|' . get_class($expr->class);
}
}

@staabm staabm Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff of my impl vs. native acceleration PR: I made the key unique enough so it can also cache CallLike

@ondrejmirtes

Copy link
Copy Markdown
Member

Yeah, please merge your no-WeakMap version (here), I'll rework the turbo-ext.

This reverts commit 84c3095.
@staabm staabm changed the title Memoize ExprHandler dispatch in MutatingScope::resolveType() Memoize ExprHandler dispatch in MutatingScope::resolveType() Jul 12, 2026
@staabm staabm merged commit 324cd4e into phpstan:2.2.x Jul 12, 2026
670 of 671 checks passed
@staabm staabm deleted the reg branch July 12, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants