Skip to content

Remember annotation method and property lookups that find nothing - #6978

Merged
ondrejmirtes merged 1 commit into
2.3.xfrom
memoize-annotation-lookup-misses
Nov 6, 2026
Merged

ondrejmirtes merged 1 commit into
2.3.xfrom
memoize-annotation-lookup-misses

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

AnnotationsMethodsClassReflectionExtension and AnnotationsPropertiesClassReflectionExtension look for a @method / @property tag by recursing into the parent class and every interface. ClassReflection::getInterfaces() already includes the inherited interfaces, so the same ancestors are reached along many paths. Only hits were cached, so a method with no @method tag in the hierarchy was searched again on every path, which grows exponentially with depth. PhpClassReflectionExtension::createMethod() asks for each native method, so every method call pays for it.

Both extensions now cache misses too, and the recursion into the parent and interfaces goes through that cache. The trait branch keeps calling the uncached walk, because there the declaring class is the using class and not the trait.

On tests/bench/data/phpdoc-inheritance-speedup.php (10 levels × 3 interfaces), this walk was 74 % of the analysis time.

Measurements

Instruction counts come from CodSpeed's CPU simulation (the harness from #6974, PHP 8.5.12):

instructions estimatedCycles
2.3.x 118,485 M 120,783 M
this PR 8,374 M 9,418 M

Wall clock: bin/phpstan analyse --debug, median of 6 interleaved runs per side, result cache cleared before each run, no turbo extension. Amp orb, Intel Xeon @ 2.60 GHz, 16 vCPUs, PHP 8.5.12 NTS:

wall clock
2.3.x 11.98 s
this PR 3.11 s (3.9× faster)

The benchmark still catches the regression it was written for (#4829): with this change applied on top of the commit before #4829, it takes 117 s user time, against 3.9 s on top of #4829 itself.

Under the CPU simulation it takes about 4 minutes instead of 39, so the longest benchmark shard in #6974 gets much shorter.

Neither class is shadowed by the turbo extension.

The lookup recurses into the parent class and every interface, and
getInterfaces() already includes the inherited ones, so the same
ancestors are reached over and over. Only hits were cached, so a method
without a @method tag anywhere in a wide, deep hierarchy was searched
along every path again.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1ad6c-dec2-72ff-bbe8-5b3b62746c94
@ondrejmirtes
ondrejmirtes merged commit 8af6dd7 into 2.3.x Nov 6, 2026
594 checks passed
@ondrejmirtes
ondrejmirtes deleted the memoize-annotation-lookup-misses branch November 6, 2026 19:18
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.

1 participant