diff --git a/bench/unused_import_removing_bench.php b/bench/unused_import_removing_bench.php new file mode 100644 index 00000000000..2cb5e010edc --- /dev/null +++ b/bench/unused_import_removing_bench.php @@ -0,0 +1,98 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +$sourceFilePath = __DIR__ . '/Source/ManyNamesSource.php'; + +/** @var UnusedImportRemovingPostRector $postRector */ +$postRector = $rectorConfig->make(UnusedImportRemovingPostRector::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; +}; + +$file = $testingParser->parseFilePathToFile($sourceFilePath); +$fileNode = $file->getFileNode(); +assert($fileNode instanceof FileNode); + +// the root nodes enterNode() actually hits per file: the FileNode and the inner Namespace_ +$rootNodes = [$fileNode]; +foreach ($fileNode->stmts as $stmt) { + if ($stmt instanceof Namespace_) { + $rootNodes[] = $stmt; + } +} + +$run = static function () use ($postRector, $rootNodes): void { + for ($enter = 0; $enter < ENTERS; ++$enter) { + foreach ($rootNodes as $rootNode) { + $postRector->enterNode($rootNode); + } + } +}; + +$measure = static function () use ($run, $cpuSeconds): array { + // warm up autoloading / parser once, excluded from timing + $run(); + + $cpuStart = $cpuSeconds(); + $wallStart = hrtime(true); + + for ($repeat = 0; $repeat < REPEAT; ++$repeat) { + $run(); + } + + return [ + 'cpu' => $cpuSeconds() - $cpuStart, + 'wall' => (hrtime(true) - $wallStart) / 1_000_000_000, + ]; +}; + +assert(class_exists(File::class)); + +$result = $measure(); + +printf('UnusedImportRemovingPostRector::enterNode benchmark%s', PHP_EOL); +printf(' root nodes=%d enters=%d repeat=%d%s', count($rootNodes), ENTERS, REPEAT, PHP_EOL); +printf('%s', PHP_EOL); +printf(' enterNode cpu=%.4fs wall=%.4fs%s', $result['cpu'], $result['wall'], PHP_EOL); diff --git a/src/PostRector/Rector/UnusedImportRemovingPostRector.php b/src/PostRector/Rector/UnusedImportRemovingPostRector.php index 120c30b3520..f45fc2f640a 100644 --- a/src/PostRector/Rector/UnusedImportRemovingPostRector.php +++ b/src/PostRector/Rector/UnusedImportRemovingPostRector.php @@ -36,6 +36,11 @@ public function enterNode(Node $node): ?Node return null; } + // no use import at this level → nothing to remove, skip the full name resolution walks + if (! $this->hasUseStmt($node->stmts)) { + return null; + } + $hasChanged = false; $namespaceOriginalCase = $node instanceof Namespace_ && $node->name instanceof Name ? $node->name->toString() @@ -98,6 +103,14 @@ public function enterNode(Node $node): ?Node return $node; } + /** + * @param Node[] $stmts + */ + private function hasUseStmt(array $stmts): bool + { + return array_any($stmts, fn(Node $stmt): bool => $stmt instanceof Use_); + } + /** * @return string[] */