From 5dd707501cbd5db13905714fc29377a5941623e7 Mon Sep 17 00:00:00 2001 From: Alfred Bez <1001186+alfredbez@users.noreply.github.com> Date: Sun, 18 Oct 2026 06:54:30 +0000 Subject: [PATCH] perf: skip known-used private methods early --- bench/remove_unused_private_method_bench.php | 86 +++++++++++++++++++ ...mixed_protected_and_unused_methods.php.inc | 69 +++++++++++++++ .../RemoveUnusedPrivateMethodRector.php | 13 +-- 3 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 bench/remove_unused_private_method_bench.php create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/mixed_protected_and_unused_methods.php.inc diff --git a/bench/remove_unused_private_method_bench.php b/bench/remove_unused_private_method_bench.php new file mode 100644 index 00000000000..a87eb585cac --- /dev/null +++ b/bench/remove_unused_private_method_bench.php @@ -0,0 +1,86 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$rector = $rectorConfig->make(RemoveUnusedPrivateMethodRector::class); + +$source = "locallyUsed();\n"; + $source .= " }\n\n"; + $source .= " private static function provideValues(): array\n {\n return [[42]];\n }\n\n"; + $source .= " private static function expectedValue(): int\n {\n return 42;\n }\n\n"; + $source .= " private function locallyUsed(): void\n {\n }\n"; + $source .= "}\n\n"; +} + +$tempBase = tempnam(sys_get_temp_dir(), 'bench_'); +$sourceFilePath = $tempBase . '.php'; +file_put_contents($sourceFilePath, $source); + +$stmts = $testingParser->parseFileToDecoratedNodes($sourceFilePath); + +/** @var Class_[] $classes */ +$classes = $betterNodeFinder->findInstanceOf($stmts, Class_::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, $classes): void { + foreach ($classes as $class) { + $rector->refactor($class); + } +}; + +$run(); + +$wallStart = microtime(true); +$cpuStart = $cpuSeconds(); + +for ($repeat = 0; $repeat < REPEAT; ++$repeat) { + $run(); +} + +$cpu = $cpuSeconds() - $cpuStart; +$wall = microtime(true) - $wallStart; + +if (is_file($sourceFilePath)) { + unlink($sourceFilePath); +} + +if (is_file($tempBase)) { + unlink($tempBase); +} + +$totalCalls = REPEAT * count($classes); + +printf('classes: %d, repeats: %d, refactor() calls: %d%s', count($classes), 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-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/mixed_protected_and_unused_methods.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/mixed_protected_and_unused_methods.php.inc new file mode 100644 index 00000000000..a20f6761604 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/mixed_protected_and_unused_methods.php.inc @@ -0,0 +1,69 @@ +locallyUsed(); + } + + private static function provideValues(): array + { + return [[42]]; + } + + private static function expectedValue(): int + { + return 42; + } + + private function unused(): void + { + } + + private function locallyUsed(): void + { + } +} +----- +locallyUsed(); + } + + private static function provideValues(): array + { + return [[42]]; + } + + private static function expectedValue(): int + { + return 42; + } + + private function locallyUsed(): void + { + } +} diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php index 21309d71f2b..09cc2c28597 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php @@ -102,6 +102,7 @@ public function refactor(Node $node): ?Node // methods invoked via a class-string static call, e.g. self::class::sampleClass(), // are not seen by the usage analyzer $classStringCallMethodNames = $this->resolveClassStringStaticCallNames($node); + $scope = null; foreach ($node->stmts as $classStmtKey => $classStmt) { if (! $classStmt instanceof ClassMethod) { @@ -113,24 +114,24 @@ public function refactor(Node $node): ?Node } $classMethod = $classStmt; - if ($this->hasDynamicMethodCallOnFetchThis($classStmt)) { + if ($this->isNames($classMethod, $dataProviderMethodNames)) { continue; } - $scope = ScopeFetcher::fetch($node); - if ($this->shouldSkip($classStmt, $classReflection)) { + if ($this->isNames($classMethod, $classStringCallMethodNames)) { continue; } - if ($this->isClassMethodUsedAnalyzer->isClassMethodUsed($node, $classStmt, $scope)) { + if ($this->hasDynamicMethodCallOnFetchThis($classStmt)) { continue; } - if ($this->isNames($classMethod, $dataProviderMethodNames)) { + if ($this->shouldSkip($classStmt, $classReflection)) { continue; } - if ($this->isNames($classMethod, $classStringCallMethodNames)) { + $scope ??= ScopeFetcher::fetch($node); + if ($this->isClassMethodUsedAnalyzer->isClassMethodUsed($node, $classStmt, $scope)) { continue; }