From 0a92717da398c640cc46f60e4bbfc101c0b0f8d4 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 20 Oct 2026 21:48:26 +0200 Subject: [PATCH] [perf] Key config by old name in 7 Transform rules ConstFetchToClassConstFetchRector, FuncCallToStaticCallRector, FuncCallToNewRector, StaticCallToFuncCallRector, StaticCallToNewRector, MethodCallToFuncCallRector and MethodCallToStaticCallRector each scanned their whole configured list/map per visited node, running isName() on each entry. Resolve the node name once and look it up by lowercased name (keyed map, or a bucket built in configure()). Any object-type / class check stays inside the matched branch. The lowercased key matches the case-insensitive isName() lookup these rules already used, so behaviour is unchanged. bench/transform_funccall_to_staticcall_bucket_bench.php (400 entries, 200 non-matching calls): 1875.1 -> 3.4 us/call. --- ...rm_funccall_to_staticcall_bucket_bench.php | 100 ++++++++++++++++++ .../ConstFetchToClassConstFetchRector.php | 19 ++-- .../Rector/FuncCall/FuncCallToNewRector.php | 20 ++-- .../FuncCall/FuncCallToStaticCallRector.php | 19 ++-- .../MethodCall/MethodCallToFuncCallRector.php | 19 ++-- .../MethodCallToStaticCallRector.php | 18 ++-- .../StaticCall/StaticCallToFuncCallRector.php | 18 ++-- .../StaticCall/StaticCallToNewRector.php | 20 ++-- 8 files changed, 183 insertions(+), 50 deletions(-) create mode 100644 bench/transform_funccall_to_staticcall_bucket_bench.php diff --git a/bench/transform_funccall_to_staticcall_bucket_bench.php b/bench/transform_funccall_to_staticcall_bucket_bench.php new file mode 100644 index 00000000000..7a8dbe5052e --- /dev/null +++ b/bench/transform_funccall_to_staticcall_bucket_bench.php @@ -0,0 +1,100 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$rector = $rectorConfig->make(FuncCallToStaticCallRector::class); + +$funcCallsToStaticCalls = []; +for ($i = 0; $i < RENAMES; ++$i) { + $funcCallsToStaticCalls[] = new FuncCallToStaticCall('old_function_' . $i, 'Bench\\SomeClass' . $i, 'method' . $i); +} + +$rector->configure($funcCallsToStaticCalls); + +$source = "parseFileToDecoratedNodes($sourceFilePath); + +/** @var FuncCall[] $funcCalls */ +$funcCalls = $betterNodeFinder->findInstanceOf($stmts, FuncCall::class); + +$cpuSeconds = static function (): float { + $usage = getrusage(); + return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000 + + $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000; +}; + +$run = static function () use ($rector, $funcCalls): void { + foreach ($funcCalls as $funcCall) { + $rector->refactor($funcCall); + } +}; + +// warm up autoloading/reflection caches once, excluded from timing +$run(); + +$wallStart = microtime(true); +$cpuStart = $cpuSeconds(); + +for ($repeat = 0; $repeat < REPEAT; ++$repeat) { + $run(); +} + +$cpu = $cpuSeconds() - $cpuStart; +$wall = microtime(true) - $wallStart; + +@unlink($sourceFilePath); + +$totalCalls = REPEAT * count($funcCalls); + +printf('entries: %d, calls: %d, repeats: %d, refactor() calls: %d%s', RENAMES, count($funcCalls), REPEAT, $totalCalls, PHP_EOL); +printf('CPU: %.4f s (%.3f us/call)%s', $cpu, $cpu / $totalCalls * 1_000_000, PHP_EOL); +printf('wall: %.4f s%s', $wall, PHP_EOL); diff --git a/rules/Transform/Rector/ConstFetch/ConstFetchToClassConstFetchRector.php b/rules/Transform/Rector/ConstFetch/ConstFetchToClassConstFetchRector.php index 6cab06af2be..deeba50f368 100644 --- a/rules/Transform/Rector/ConstFetch/ConstFetchToClassConstFetchRector.php +++ b/rules/Transform/Rector/ConstFetch/ConstFetchToClassConstFetchRector.php @@ -20,9 +20,9 @@ final class ConstFetchToClassConstFetchRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var ConstFetchToClassConstFetch[] + * @var array */ - private array $constFetchToClassConsts = []; + private array $constFetchToClassConstsByOldName = []; public function getRuleDefinition(): RuleDefinition { @@ -43,11 +43,12 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?ClassConstFetch { - foreach ($this->constFetchToClassConsts as $constFetchToClassConst) { - if (! $this->isName($node, $constFetchToClassConst->getOldConstName())) { - continue; - } + $constName = $this->getName($node); + if ($constName === null) { + return null; + } + foreach ($this->constFetchToClassConstsByOldName[strtolower($constName)] ?? [] as $constFetchToClassConst) { return $this->nodeFactory->createClassConstFetch( $constFetchToClassConst->getNewClassName(), $constFetchToClassConst->getNewConstName() @@ -60,6 +61,10 @@ public function refactor(Node $node): ?ClassConstFetch public function configure(array $configuration): void { Assert::allIsAOf($configuration, ConstFetchToClassConstFetch::class); - $this->constFetchToClassConsts = $configuration; + + $this->constFetchToClassConstsByOldName = []; + foreach ($configuration as $constFetchToClassConst) { + $this->constFetchToClassConstsByOldName[strtolower($constFetchToClassConst->getOldConstName())][] = $constFetchToClassConst; + } } } diff --git a/rules/Transform/Rector/FuncCall/FuncCallToNewRector.php b/rules/Transform/Rector/FuncCall/FuncCallToNewRector.php index c4386ccb5ff..94d00173c97 100644 --- a/rules/Transform/Rector/FuncCall/FuncCallToNewRector.php +++ b/rules/Transform/Rector/FuncCall/FuncCallToNewRector.php @@ -68,15 +68,17 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->functionToNew as $function => $new) { - if (! $this->isName($node, $function)) { - continue; - } + $funcName = $this->getName($node); + if ($funcName === null) { + return null; + } - return new New_(new FullyQualified($new), $node->args); + $new = $this->functionToNew[strtolower($funcName)] ?? null; + if ($new === null) { + return null; } - return null; + return new New_(new FullyQualified($new), $node->args); } /** @@ -86,6 +88,10 @@ public function configure(array $configuration): void { Assert::allString($configuration); - $this->functionToNew = $configuration; + // keyed by lowercased function name (function names are case-insensitive) + $this->functionToNew = []; + foreach ($configuration as $function => $new) { + $this->functionToNew[strtolower($function)] = $new; + } } } diff --git a/rules/Transform/Rector/FuncCall/FuncCallToStaticCallRector.php b/rules/Transform/Rector/FuncCall/FuncCallToStaticCallRector.php index a26e852a855..7816d60a9cd 100644 --- a/rules/Transform/Rector/FuncCall/FuncCallToStaticCallRector.php +++ b/rules/Transform/Rector/FuncCall/FuncCallToStaticCallRector.php @@ -19,9 +19,9 @@ final class FuncCallToStaticCallRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var FuncCallToStaticCall[] + * @var array */ - private array $funcCallsToStaticCalls = []; + private array $funcCallsToStaticCallsByOldFunc = []; public function getRuleDefinition(): RuleDefinition { @@ -47,11 +47,12 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->funcCallsToStaticCalls as $funcCallToStaticCall) { - if (! $this->isName($node, $funcCallToStaticCall->getOldFuncName())) { - continue; - } + $funcName = $this->getName($node); + if ($funcName === null) { + return null; + } + foreach ($this->funcCallsToStaticCallsByOldFunc[strtolower($funcName)] ?? [] as $funcCallToStaticCall) { return $this->nodeFactory->createStaticCall( $funcCallToStaticCall->getNewClassName(), $funcCallToStaticCall->getNewMethodName(), @@ -69,6 +70,10 @@ public function configure(array $configuration): void { Assert::allIsAOf($configuration, FuncCallToStaticCall::class); - $this->funcCallsToStaticCalls = $configuration; + // bucket by lowercased old function name (function names are case-insensitive) + $this->funcCallsToStaticCallsByOldFunc = []; + foreach ($configuration as $funcCallToStaticCall) { + $this->funcCallsToStaticCallsByOldFunc[strtolower($funcCallToStaticCall->getOldFuncName())][] = $funcCallToStaticCall; + } } } diff --git a/rules/Transform/Rector/MethodCall/MethodCallToFuncCallRector.php b/rules/Transform/Rector/MethodCall/MethodCallToFuncCallRector.php index db327d29cf9..34d646a03f6 100644 --- a/rules/Transform/Rector/MethodCall/MethodCallToFuncCallRector.php +++ b/rules/Transform/Rector/MethodCall/MethodCallToFuncCallRector.php @@ -23,9 +23,9 @@ final class MethodCallToFuncCallRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var MethodCallToFuncCall[] + * @var array */ - private array $methodCallsToFuncCalls = []; + private array $methodCallsToFuncCallsByMethod = []; public function getRuleDefinition(): RuleDefinition { @@ -74,11 +74,12 @@ public function refactor(Node $node): ?Node return null; } - foreach ($this->methodCallsToFuncCalls as $methodCallToFuncCall) { - if (! $this->isName($node->name, $methodCallToFuncCall->getMethodName())) { - continue; - } + $methodName = $this->getName($node->name); + if ($methodName === null) { + return null; + } + foreach ($this->methodCallsToFuncCallsByMethod[strtolower($methodName)] ?? [] as $methodCallToFuncCall) { if (! $this->isObjectType($node->var, new ObjectType($methodCallToFuncCall->getObjectType()))) { continue; } @@ -95,6 +96,10 @@ public function refactor(Node $node): ?Node public function configure(array $configuration): void { Assert::allIsInstanceOf($configuration, MethodCallToFuncCall::class); - $this->methodCallsToFuncCalls = $configuration; + + $this->methodCallsToFuncCallsByMethod = []; + foreach ($configuration as $methodCallToFuncCall) { + $this->methodCallsToFuncCallsByMethod[strtolower($methodCallToFuncCall->getMethodName())][] = $methodCallToFuncCall; + } } } diff --git a/rules/Transform/Rector/MethodCall/MethodCallToStaticCallRector.php b/rules/Transform/Rector/MethodCall/MethodCallToStaticCallRector.php index 066a1e09d42..b13779acfec 100644 --- a/rules/Transform/Rector/MethodCall/MethodCallToStaticCallRector.php +++ b/rules/Transform/Rector/MethodCall/MethodCallToStaticCallRector.php @@ -19,9 +19,9 @@ final class MethodCallToStaticCallRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var MethodCallToStaticCall[] + * @var array */ - private array $methodCallsToStaticCalls = []; + private array $methodCallsToStaticCallsByOldMethod = []; public function getRuleDefinition(): RuleDefinition { @@ -79,11 +79,12 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->methodCallsToStaticCalls as $methodCallToStaticCall) { - if (! $this->isName($node->name, $methodCallToStaticCall->getOldMethod())) { - continue; - } + $methodName = $this->getName($node->name); + if ($methodName === null) { + return null; + } + foreach ($this->methodCallsToStaticCallsByOldMethod[strtolower($methodName)] ?? [] as $methodCallToStaticCall) { if (! $this->isObjectType($node->var, $methodCallToStaticCall->getOldObjectType())) { continue; } @@ -105,6 +106,9 @@ public function configure(array $configuration): void { Assert::allIsAOf($configuration, MethodCallToStaticCall::class); - $this->methodCallsToStaticCalls = $configuration; + $this->methodCallsToStaticCallsByOldMethod = []; + foreach ($configuration as $methodCallToStaticCall) { + $this->methodCallsToStaticCallsByOldMethod[strtolower($methodCallToStaticCall->getOldMethod())][] = $methodCallToStaticCall; + } } } diff --git a/rules/Transform/Rector/StaticCall/StaticCallToFuncCallRector.php b/rules/Transform/Rector/StaticCall/StaticCallToFuncCallRector.php index ee1630e0cf9..a35a3963d5f 100644 --- a/rules/Transform/Rector/StaticCall/StaticCallToFuncCallRector.php +++ b/rules/Transform/Rector/StaticCall/StaticCallToFuncCallRector.php @@ -21,9 +21,9 @@ final class StaticCallToFuncCallRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var StaticCallToFuncCall[] + * @var array */ - private array $staticCallsToFunctions = []; + private array $staticCallsToFunctionsByMethod = []; public function getRuleDefinition(): RuleDefinition { @@ -49,11 +49,12 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->staticCallsToFunctions as $staticCallToFunction) { - if (! $this->isName($node->name, $staticCallToFunction->getMethod())) { - continue; - } + $methodName = $this->getName($node->name); + if ($methodName === null) { + return null; + } + foreach ($this->staticCallsToFunctionsByMethod[strtolower($methodName)] ?? [] as $staticCallToFunction) { if (! $this->isObjectType($node->class, $staticCallToFunction->getObjectType())) { continue; } @@ -71,6 +72,9 @@ public function configure(array $configuration): void { Assert::allIsAOf($configuration, StaticCallToFuncCall::class); - $this->staticCallsToFunctions = $configuration; + $this->staticCallsToFunctionsByMethod = []; + foreach ($configuration as $staticCallToFunction) { + $this->staticCallsToFunctionsByMethod[strtolower($staticCallToFunction->getMethod())][] = $staticCallToFunction; + } } } diff --git a/rules/Transform/Rector/StaticCall/StaticCallToNewRector.php b/rules/Transform/Rector/StaticCall/StaticCallToNewRector.php index 2258473bccd..fa53d580fe5 100644 --- a/rules/Transform/Rector/StaticCall/StaticCallToNewRector.php +++ b/rules/Transform/Rector/StaticCall/StaticCallToNewRector.php @@ -21,9 +21,9 @@ final class StaticCallToNewRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var StaticCallToNew[] + * @var array */ - private array $staticCallsToNews = []; + private array $staticCallsToNewsByMethod = []; public function getRuleDefinition(): RuleDefinition { @@ -67,12 +67,13 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->staticCallsToNews as $staticCallToNew) { - if (! $this->isName($node->class, $staticCallToNew->getClass())) { - continue; - } + $methodName = $this->getName($node->name); + if ($methodName === null) { + return null; + } - if (! $this->isName($node->name, $staticCallToNew->getMethod())) { + foreach ($this->staticCallsToNewsByMethod[strtolower($methodName)] ?? [] as $staticCallToNew) { + if (! $this->isName($node->class, $staticCallToNew->getClass())) { continue; } @@ -94,6 +95,9 @@ public function configure(array $configuration): void { Assert::allIsAOf($configuration, StaticCallToNew::class); - $this->staticCallsToNews = $configuration; + $this->staticCallsToNewsByMethod = []; + foreach ($configuration as $staticCallToNew) { + $this->staticCallsToNewsByMethod[strtolower($staticCallToNew->getMethod())][] = $staticCallToNew; + } } }