From 7db2030e7ecab86bb4a08afbfab236957baada7c Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 20 Oct 2026 21:54:56 +0200 Subject: [PATCH] [perf] Key config by old name in Arguments/Removing rules RemoveMethodCallParamRector, ArgumentAdderRector and ArgumentRemoverRector each scanned their whole configured value-object list per visited node, running isName() on each entry before the object-type check. Bucket the list by lowercased old method name once in configure(). RemoveFuncCallRector flips its flat function-name list to a lowercased name set. The lowercased key matches the case-insensitive isName() lookup these rules already used, so behaviour is unchanged. bench/remove_method_call_param_bucket_bench.php (400 entries, 200 non-matching calls): 1000.6 -> 1.8 us/call. --- .../remove_method_call_param_bucket_bench.php | 99 +++++++++++++++++++ .../ClassMethod/ArgumentAdderRector.php | 25 ++--- .../RemoveMethodCallParamRector.php | 19 ++-- .../ClassMethod/ArgumentRemoverRector.php | 19 ++-- .../Rector/FuncCall/RemoveFuncCallRector.php | 24 +++-- 5 files changed, 151 insertions(+), 35 deletions(-) create mode 100644 bench/remove_method_call_param_bucket_bench.php diff --git a/bench/remove_method_call_param_bucket_bench.php b/bench/remove_method_call_param_bucket_bench.php new file mode 100644 index 00000000000..13108963536 --- /dev/null +++ b/bench/remove_method_call_param_bucket_bench.php @@ -0,0 +1,99 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$rector = $rectorConfig->make(RemoveMethodCallParamRector::class); + +$removeMethodCallParams = []; +for ($i = 0; $i < ENTRIES; ++$i) { + $removeMethodCallParams[] = new RemoveMethodCallParam('Bench\\SomeClass' . $i, 'oldMethod' . $i, 0); +} + +$rector->configure($removeMethodCallParams); + +$source = "freshCall{$i}(1, 2);\n"; +} + +$sourceFilePath = tempnam(sys_get_temp_dir(), 'bench_') . '.php'; +file_put_contents($sourceFilePath, $source); + +$stmts = $testingParser->parseFileToDecoratedNodes($sourceFilePath); + +/** @var MethodCall[] $methodCalls */ +$methodCalls = $betterNodeFinder->findInstanceOf($stmts, MethodCall::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, $methodCalls): void { + foreach ($methodCalls as $methodCall) { + $rector->refactor($methodCall); + } +}; + +// 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($methodCalls); + +printf('entries: %d, calls: %d, repeats: %d, refactor() calls: %d%s', ENTRIES, count($methodCalls), 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/Arguments/Rector/ClassMethod/ArgumentAdderRector.php b/rules/Arguments/Rector/ClassMethod/ArgumentAdderRector.php index 1e63816d6a8..956bb4e1b4f 100644 --- a/rules/Arguments/Rector/ClassMethod/ArgumentAdderRector.php +++ b/rules/Arguments/Rector/ClassMethod/ArgumentAdderRector.php @@ -40,9 +40,9 @@ final class ArgumentAdderRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var ArgumentAdder[]|ArgumentAdderWithoutDefaultValue[] + * @var array> */ - private array $addedArguments = []; + private array $addedArgumentsByMethod = []; private bool $hasChanged = false; @@ -136,7 +136,11 @@ public function refactor(Node $node): MethodCall|StaticCall|Class_|null public function configure(array $configuration): void { Assert::allIsAnyOf($configuration, [ArgumentAdder::class, ArgumentAdderWithoutDefaultValue::class]); - $this->addedArguments = $configuration; + + $this->addedArgumentsByMethod = []; + foreach ($configuration as $addedArgument) { + $this->addedArgumentsByMethod[strtolower($addedArgument->getMethod())][] = $addedArgument; + } } private function isObjectTypeMatch(MethodCall|StaticCall $call, ObjectType $objectType): bool @@ -380,11 +384,7 @@ private function refactorCall(StaticCall|MethodCall $call): void return; } - foreach ($this->addedArguments as $addedArgument) { - if (! $this->nodeNameResolver->isStringName($callName, $addedArgument->getMethod())) { - continue; - } - + foreach ($this->addedArgumentsByMethod[strtolower($callName)] ?? [] as $addedArgument) { if (! $this->isObjectTypeMatch($call, $addedArgument->getObjectType())) { continue; } @@ -395,11 +395,12 @@ private function refactorCall(StaticCall|MethodCall $call): void private function refactorClassMethod(Class_ $class, ClassMethod $classMethod): void { - foreach ($this->addedArguments as $addedArgument) { - if (! $this->isName($classMethod, $addedArgument->getMethod())) { - continue; - } + $methodName = $this->getName($classMethod); + if ($methodName === null) { + return; + } + foreach ($this->addedArgumentsByMethod[strtolower($methodName)] ?? [] as $addedArgument) { if (! $this->isObjectType($class, $addedArgument->getObjectType())) { continue; } diff --git a/rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php b/rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php index 2da98da7274..ae6bbe80ea6 100644 --- a/rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php +++ b/rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php @@ -26,9 +26,9 @@ final class RemoveMethodCallParamRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var RemoveMethodCallParam[] + * @var array */ - private array $removeMethodCallParams = []; + private array $removeMethodCallParamsByMethod = []; public function __construct( private readonly ReflectionResolver $reflectionResolver, @@ -84,11 +84,12 @@ public function refactor(Node $node): ?Node return null; } - foreach ($this->removeMethodCallParams as $removeMethodCallParam) { - if (! $this->isName($node->name, $removeMethodCallParam->getMethodName())) { - continue; - } + $methodName = $this->getName($node->name); + if ($methodName === null) { + return null; + } + foreach ($this->removeMethodCallParamsByMethod[strtolower($methodName)] ?? [] as $removeMethodCallParam) { if (! $this->isCallerObjectType($node, $removeMethodCallParam)) { continue; } @@ -147,7 +148,11 @@ public function refactor(Node $node): ?Node public function configure(array $configuration): void { Assert::allIsInstanceOf($configuration, RemoveMethodCallParam::class); - $this->removeMethodCallParams = $configuration; + + $this->removeMethodCallParamsByMethod = []; + foreach ($configuration as $removeMethodCallParam) { + $this->removeMethodCallParamsByMethod[strtolower($removeMethodCallParam->getMethodName())][] = $removeMethodCallParam; + } } private function isCallerObjectType(StaticCall|MethodCall $call, RemoveMethodCallParam $removeMethodCallParam): bool diff --git a/rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php b/rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php index 36d6b688686..99b4fe9e77f 100644 --- a/rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php +++ b/rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php @@ -25,9 +25,9 @@ final class ArgumentRemoverRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var ArgumentRemover[] + * @var array */ - private array $removedArguments = []; + private array $removedArgumentsByMethod = []; private bool $hasChanged = false; @@ -73,11 +73,12 @@ public function refactor(Node $node): MethodCall|StaticCall|ClassMethod|null { $this->hasChanged = false; - foreach ($this->removedArguments as $removedArgument) { - if (! $this->isName($node->name, $removedArgument->getMethod())) { - continue; - } + $methodName = $this->getName($node->name); + if ($methodName === null) { + return null; + } + foreach ($this->removedArgumentsByMethod[strtolower($methodName)] ?? [] as $removedArgument) { if (! $this->nodeTypeResolver->isMethodStaticCallOrClassMethodObjectType( $node, $removedArgument->getObjectType() @@ -101,7 +102,11 @@ public function refactor(Node $node): MethodCall|StaticCall|ClassMethod|null public function configure(array $configuration): void { Assert::allIsAOf($configuration, ArgumentRemover::class); - $this->removedArguments = $configuration; + + $this->removedArgumentsByMethod = []; + foreach ($configuration as $removedArgument) { + $this->removedArgumentsByMethod[strtolower($removedArgument->getMethod())][] = $removedArgument; + } } private function processPosition( diff --git a/rules/Removing/Rector/FuncCall/RemoveFuncCallRector.php b/rules/Removing/Rector/FuncCall/RemoveFuncCallRector.php index 0795a8e2cc2..8aedcdd7d8e 100644 --- a/rules/Removing/Rector/FuncCall/RemoveFuncCallRector.php +++ b/rules/Removing/Rector/FuncCall/RemoveFuncCallRector.php @@ -20,9 +20,10 @@ final class RemoveFuncCallRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var string[] + * keyed by lowercased function name (function names are case-insensitive) + * @var array */ - private array $removedFunctions = []; + private array $removedFunctionNames = []; public function getRuleDefinition(): RuleDefinition { @@ -60,15 +61,16 @@ public function refactor(Node $node): ?int return null; } - foreach ($this->removedFunctions as $removedFunction) { - if (! $this->isName($expr->name, $removedFunction)) { - continue; - } + $funcName = $this->getName($expr->name); + if ($funcName === null) { + return null; + } - return NodeVisitor::REMOVE_NODE; + if (! isset($this->removedFunctionNames[strtolower($funcName)])) { + return null; } - return null; + return NodeVisitor::REMOVE_NODE; } /** @@ -77,6 +79,10 @@ public function refactor(Node $node): ?int public function configure(array $configuration): void { Assert::allString($configuration); - $this->removedFunctions = $configuration; + + $this->removedFunctionNames = []; + foreach ($configuration as $removedFunction) { + $this->removedFunctionNames[strtolower($removedFunction)] = true; + } } }