From 58f7867096cddbd1ba0160907e564fc59f0bb611 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 20 Oct 2026 22:02:23 +0200 Subject: [PATCH] [perf] Key config by method/property name in Add*Type rules AddParamTypeDeclarationRector, AddPropertyTypeDeclarationRector and AddReturnTypeDeclarationRector each scanned their whole configured list per visited node, running isName()/isStringName() per entry before the class/object-type check. Bucket the list by lowercased old name once in configure(), so a node only visits the entries sharing its name. AddReturnTypeDeclarationRector dropped its per-node classMethodNames presence hash + full config scan in favour of iterating the class methods and looking each up in the bucket. The lowercased key matches the case-insensitive isName() lookup these rules already used, so behaviour is unchanged. bench/add_property_type_bucket_bench.php (400 entries, 150 non-matching properties): 1063.7 -> 2.4 us/call. --- bench/add_property_type_bucket_bench.php | 102 ++++++++++++++++++ .../AddParamTypeDeclarationRector.php | 17 +-- .../AddReturnTypeDeclarationRector.php | 28 ++--- .../AddPropertyTypeDeclarationRector.php | 19 ++-- 4 files changed, 135 insertions(+), 31 deletions(-) create mode 100644 bench/add_property_type_bucket_bench.php diff --git a/bench/add_property_type_bucket_bench.php b/bench/add_property_type_bucket_bench.php new file mode 100644 index 00000000000..cf59055f05b --- /dev/null +++ b/bench/add_property_type_bucket_bench.php @@ -0,0 +1,102 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$rector = $rectorConfig->make(AddPropertyTypeDeclarationRector::class); + +$addPropertyTypeDeclarations = []; +for ($i = 0; $i < ENTRIES; ++$i) { + $addPropertyTypeDeclarations[] = new AddPropertyTypeDeclaration('Bench\\SomeParent' . $i, 'oldProperty' . $i, new StringType()); +} + +$rector->configure($addPropertyTypeDeclarations); + +$source = "parseFileToDecoratedNodes($sourceFilePath); + +/** @var Property[] $properties */ +$properties = $betterNodeFinder->findInstanceOf($stmts, Property::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, $properties): void { + foreach ($properties as $property) { + $rector->refactor($property); + } +}; + +// 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($properties); + +printf('entries: %d, properties: %d, repeats: %d, refactor() calls: %d%s', ENTRIES, count($properties), 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/TypeDeclaration/Rector/ClassMethod/AddParamTypeDeclarationRector.php b/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeDeclarationRector.php index c7f08102e6b..c4b491a2ecb 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeDeclarationRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeDeclarationRector.php @@ -31,9 +31,9 @@ final class AddParamTypeDeclarationRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var AddParamTypeDeclaration[] + * @var array */ - private array $addParamTypeDeclarations = []; + private array $addParamTypeDeclarationsByMethod = []; private bool $hasChanged = false; @@ -103,11 +103,11 @@ public function refactor(Node $node): ?Node } $methodName = $this->getName($classMethod); - foreach ($this->addParamTypeDeclarations as $addParamTypeDeclaration) { - if (! $this->nodeNameResolver->isStringName($methodName, $addParamTypeDeclaration->getMethodName())) { - continue; - } + if ($methodName === null) { + continue; + } + foreach ($this->addParamTypeDeclarationsByMethod[strtolower($methodName)] ?? [] as $addParamTypeDeclaration) { if (! $this->isObjectType($node, $addParamTypeDeclaration->getObjectType())) { continue; } @@ -130,7 +130,10 @@ public function configure(array $configuration): void { Assert::allIsAOf($configuration, AddParamTypeDeclaration::class); - $this->addParamTypeDeclarations = $configuration; + $this->addParamTypeDeclarationsByMethod = []; + foreach ($configuration as $addParamTypeDeclaration) { + $this->addParamTypeDeclarationsByMethod[strtolower($addParamTypeDeclaration->getMethodName())][] = $addParamTypeDeclaration; + } } private function shouldSkip(Class_|Interface_ $classLike, ClassMethod $classMethod): bool diff --git a/rules/TypeDeclaration/Rector/ClassMethod/AddReturnTypeDeclarationRector.php b/rules/TypeDeclaration/Rector/ClassMethod/AddReturnTypeDeclarationRector.php index 7780c9d7519..3c2dd6d445a 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/AddReturnTypeDeclarationRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/AddReturnTypeDeclarationRector.php @@ -33,9 +33,9 @@ final class AddReturnTypeDeclarationRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var AddReturnTypeDeclaration[] + * @var array */ - private array $methodReturnTypes = []; + private array $methodReturnTypesByMethod = []; private bool $hasChanged = false; @@ -94,24 +94,15 @@ public function refactor(Node $node): ?Node return null; } - $classMethodNames = []; foreach ($node->getMethods() as $classMethod) { - $classMethodNames[strtolower((string) $this->getName($classMethod))] = true; - } - - foreach ($this->methodReturnTypes as $methodReturnType) { - // cheap name check before the expensive type resolution below - if (! isset($classMethodNames[strtolower($methodReturnType->getMethod())])) { - continue; - } - - $objectType = $methodReturnType->getObjectType(); - if (! $this->isObjectType($node, $objectType)) { + $methodName = $this->getName($classMethod); + if ($methodName === null) { continue; } - foreach ($node->getMethods() as $classMethod) { - if (! $this->isName($classMethod, $methodReturnType->getMethod())) { + foreach ($this->methodReturnTypesByMethod[strtolower($methodName)] ?? [] as $methodReturnType) { + $objectType = $methodReturnType->getObjectType(); + if (! $this->isObjectType($node, $objectType)) { continue; } @@ -138,7 +129,10 @@ public function configure(array $configuration): void { Assert::allIsAOf($configuration, AddReturnTypeDeclaration::class); - $this->methodReturnTypes = $configuration; + $this->methodReturnTypesByMethod = []; + foreach ($configuration as $methodReturnType) { + $this->methodReturnTypesByMethod[strtolower($methodReturnType->getMethod())][] = $methodReturnType; + } } private function processClassMethodNodeWithTypehints( diff --git a/rules/TypeDeclaration/Rector/Property/AddPropertyTypeDeclarationRector.php b/rules/TypeDeclaration/Rector/Property/AddPropertyTypeDeclarationRector.php index 57d649d8f6b..0788e31324c 100644 --- a/rules/TypeDeclaration/Rector/Property/AddPropertyTypeDeclarationRector.php +++ b/rules/TypeDeclaration/Rector/Property/AddPropertyTypeDeclarationRector.php @@ -25,9 +25,9 @@ final class AddPropertyTypeDeclarationRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var AddPropertyTypeDeclaration[] + * @var array */ - private array $addPropertyTypeDeclarations = []; + private array $addPropertyTypeDeclarationsByProperty = []; public function __construct( private readonly StaticTypeMapper $staticTypeMapper @@ -82,11 +82,12 @@ public function refactor(Node $node): ?Node return null; } - foreach ($this->addPropertyTypeDeclarations as $addPropertyTypeDeclaration) { - if (! $this->isName($node, $addPropertyTypeDeclaration->getPropertyName())) { - continue; - } + $propertyName = $this->getName($node); + if ($propertyName === null) { + return null; + } + foreach ($this->addPropertyTypeDeclarationsByProperty[strtolower($propertyName)] ?? [] as $addPropertyTypeDeclaration) { if (! $this->isClassReflectionType($classReflection, $addPropertyTypeDeclaration->getClass())) { continue; } @@ -113,7 +114,11 @@ public function refactor(Node $node): ?Node public function configure(array $configuration): void { Assert::allIsAOf($configuration, AddPropertyTypeDeclaration::class); - $this->addPropertyTypeDeclarations = $configuration; + + $this->addPropertyTypeDeclarationsByProperty = []; + foreach ($configuration as $addPropertyTypeDeclaration) { + $this->addPropertyTypeDeclarationsByProperty[strtolower($addPropertyTypeDeclaration->getPropertyName())][] = $addPropertyTypeDeclaration; + } } private function isClassReflectionType(ClassReflection $classReflection, string $type): bool