Skip to content

[perf] Bucket method renames by old name in RenameMethodRector - #8734

Merged
TomasVotruba merged 2 commits into
mainfrom
tv-perf-rename-method-bucket
Oct 20, 2026
Merged

TomasVotruba merged 2 commits into
mainfrom
tv-perf-rename-method-bucket

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

RenameMethodRector is one of the few rules with real self cost (not triggered type resolution) in profiling - it scanned the whole configured rename list per call/method node, running isStringName() on each entry before the type/reflection checks. With large sets (symfony/doctrine ship hundreds of renames) that is a per-node linear scan of pure string compares.

Change

Bucket the renames by lowercased old-method name once at configure() time. A node then only visits the renames that share its method name - a single hash lookup instead of N comparisons. Behaviour is unchanged: isStringName() still runs on the matched candidates, so the Object/case special-cases are preserved, and the type/reflection gating is untouched.

Two small cleanups on the class-rename docblock path ride along:

  • DocBlockClassRenamer reuses a single PhpDocNodeTraverser instead of allocating one per node.
  • PhpDocClassRenamer hoists a loop-invariant getValue('type') lookup out of the per-class loop.

Numbers

bench/rename_method_bucket_bench.php (400 renames, 200 non-matching calls, CPU time):

us/call
before 159.9
after 2.0

The win scales with the size of the configured rename list. Full rules-tests/Renaming suite (107 tests) green, PHPStan and ECS clean.

Per call/method node the rule scanned the whole configured rename list, running isStringName() on each before the type/reflection checks. With large sets (hundreds of renames) this is a per-node linear scan of pure string compares.

Bucket the renames by lowercased old-method name at configure() time, so a node only visits the renames sharing its name - one hash lookup instead of N comparisons. Behaviour is unchanged; isStringName() still runs on the matched candidates.

Also two small cleanups on the class-rename docblock path: reuse a single PhpDocNodeTraverser in DocBlockClassRenamer instead of allocating one per node, and hoist a loop-invariant getValue('type') lookup out of the per-class loop in PhpDocClassRenamer.

bench/rename_method_bucket_bench.php (400 renames, 200 non-matching calls): 159.9 -> 2.0 us/call.
…ClassRenamer

The JMS @type rename path rebuilt the preg_quote word-boundary pattern per configured class per node; cache it by old class name.
@TomasVotruba

Copy link
Copy Markdown
Member Author

Follow-up commit: PhpDocClassRenamer dropped readonly to memoize the JMS @Type word-boundary regex per old class instead of rebuilding the preg_quote pattern per class per node. Cold path (JMS @Type only), so not benched - cheap, behavior-preserving.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant