From 32e7203f4af54f85bf13832d4385635b220af0f0 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 20 Oct 2026 19:16:24 +0200 Subject: [PATCH] [perf] Replace per-node config scan with keyed lookup in Rename func/const/string rules RenameFunctionRector, RenameConstantRector and RenameStringRector each held a map already keyed by the matched value, yet scanned the whole map per visited node (isStringName/isName/isValue per entry). Resolve the node name/value once and look the rename up directly. - function: keyed by lowercased name (function names are case-insensitive), preserved. - constant: case-sensitive direct lookup, matching PHP constant semantics. - string: resolve the value once, then array lookup. bench/rename_function_keyed_lookup_bench.php (500 renames, 200 non-matching calls): 173.1 -> 3.3 us/call. --- bench/rename_function_keyed_lookup_bench.php | 98 +++++++++++++++++++ .../ConstFetch/RenameConstantRector.php | 17 ++-- .../Rector/FuncCall/RenameFunctionRector.php | 19 ++-- .../Rector/String_/RenameStringRector.php | 14 +-- 4 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 bench/rename_function_keyed_lookup_bench.php diff --git a/bench/rename_function_keyed_lookup_bench.php b/bench/rename_function_keyed_lookup_bench.php new file mode 100644 index 00000000000..30990f7236f --- /dev/null +++ b/bench/rename_function_keyed_lookup_bench.php @@ -0,0 +1,98 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$rector = $rectorConfig->make(RenameFunctionRector::class); + +$configuration = []; +for ($i = 0; $i < RENAMES; ++$i) { + $configuration['old_function_' . $i] = 'new_function_' . $i; +} + +$rector->configure($configuration); + +$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('renames: %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/Renaming/Rector/ConstFetch/RenameConstantRector.php b/rules/Renaming/Rector/ConstFetch/RenameConstantRector.php index 16a477679f9..2f3f3b8ebb9 100644 --- a/rules/Renaming/Rector/ConstFetch/RenameConstantRector.php +++ b/rules/Renaming/Rector/ConstFetch/RenameConstantRector.php @@ -69,16 +69,19 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->oldToNewConstants as $oldConstant => $newConstant) { - if (! $this->isName($node->name, $oldConstant)) { - continue; - } + $constantName = $this->getName($node->name); + if ($constantName === null) { + return null; + } - $node->name = new Name($newConstant); - return $node; + // constant names are case-sensitive, look the rename up directly instead of scanning the map + $newConstant = $this->oldToNewConstants[$constantName] ?? null; + if ($newConstant === null) { + return null; } - return null; + $node->name = new Name($newConstant); + return $node; } /** diff --git a/rules/Renaming/Rector/FuncCall/RenameFunctionRector.php b/rules/Renaming/Rector/FuncCall/RenameFunctionRector.php index 57d0def3d1c..250dc5a3945 100644 --- a/rules/Renaming/Rector/FuncCall/RenameFunctionRector.php +++ b/rules/Renaming/Rector/FuncCall/RenameFunctionRector.php @@ -20,6 +20,7 @@ final class RenameFunctionRector extends AbstractRector implements ConfigurableRectorInterface { /** + * keyed by lowercased old function name, as function names are case-insensitive * @var array */ private array $oldFunctionToNewFunction = []; @@ -55,16 +56,13 @@ public function refactor(Node $node): ?Node return null; } - foreach ($this->oldFunctionToNewFunction as $oldFunction => $newFunction) { - if (! $this->nodeNameResolver->isStringName($nodeName, $oldFunction)) { - continue; - } - - $node->name = $this->createName($newFunction); - return $node; + $newFunction = $this->oldFunctionToNewFunction[strtolower($nodeName)] ?? null; + if ($newFunction === null) { + return null; } - return null; + $node->name = $this->createName($newFunction); + return $node; } /** @@ -75,7 +73,10 @@ public function configure(array $configuration): void Assert::allString(array_values($configuration)); Assert::allString($configuration); - $this->oldFunctionToNewFunction = $configuration; + $this->oldFunctionToNewFunction = []; + foreach ($configuration as $oldFunction => $newFunction) { + $this->oldFunctionToNewFunction[strtolower($oldFunction)] = $newFunction; + } } private function createName(string $newFunction): Name diff --git a/rules/Renaming/Rector/String_/RenameStringRector.php b/rules/Renaming/Rector/String_/RenameStringRector.php index 3216ebc7539..c8f5b65b9a3 100644 --- a/rules/Renaming/Rector/String_/RenameStringRector.php +++ b/rules/Renaming/Rector/String_/RenameStringRector.php @@ -72,15 +72,17 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->stringChanges as $oldValue => $newValue) { - if (! $this->valueResolver->isValue($node, $oldValue)) { - continue; - } + $value = $this->valueResolver->getValue($node); + if (! is_string($value)) { + return null; + } - return new String_($newValue); + $newValue = $this->stringChanges[$value] ?? null; + if ($newValue === null) { + return null; } - return null; + return new String_($newValue); } /**