Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Performance] Ensure call $reflector->reflectAllClasses() once on Worker take 3 #5878

Merged
merged 6 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Application/ApplicationFileProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
namespace Rector\Application;

use Nette\Utils\FileSystem as UtilsFileSystem;
use Rector\Caching\Cache;
use Rector\Caching\Detector\ChangedFilesDetector;
use Rector\Caching\Enum\CacheKey;
use Rector\Configuration\Option;
use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\Configuration\VendorMissAnalyseGuard;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
use Rector\Parallel\Application\ParallelFileProcessor;
use Rector\Provider\CurrentFileProvider;
use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment;
Expand Down Expand Up @@ -50,6 +53,8 @@ public function __construct(
private readonly FileProcessor $fileProcessor,
private readonly ArrayParametersMerger $arrayParametersMerger,
private readonly VendorMissAnalyseGuard $vendorMissAnalyseGuard,
private readonly DynamicSourceLocatorProvider $dynamicSourceLocatorProvider,
private readonly Cache $cache
) {
}

Expand All @@ -71,6 +76,10 @@ public function run(Configuration $configuration, InputInterface $input): Proces
return new ProcessResult([], []);
}

// ensure clear classnames collection caches on repetitive call
$key = CacheKey::CLASSNAMES_HASH_KEY . '_' . $this->dynamicSourceLocatorProvider->getCacheClassNameKey();
$this->cache->clean($key);

$this->configureCustomErrorHandler();

/**
Expand Down Expand Up @@ -98,6 +107,9 @@ public function run(Configuration $configuration, InputInterface $input): Proces
$preFileCallback = null;
}

// trigger cache class names collection
$this->dynamicSourceLocatorProvider->provide();

if ($configuration->isParallel()) {
$processResult = $this->runParallel($filePaths, $configuration, $input, $postFileCallback);
} else {
Expand Down
5 changes: 5 additions & 0 deletions src/Caching/Enum/CacheKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ final class CacheKey
* @var string
*/
public const FILE_HASH_KEY = 'file_hash';

/**
* @var string
*/
public const CLASSNAMES_HASH_KEY = 'classnames_hash';
}
2 changes: 1 addition & 1 deletion src/Console/Command/ProcessCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function __construct(
private readonly OutputFormatterCollector $outputFormatterCollector,
private readonly SymfonyStyle $symfonyStyle,
private readonly MemoryLimiter $memoryLimiter,
private readonly ConfigurationFactory $configurationFactory,
private readonly ConfigurationFactory $configurationFactory
) {
parent::__construct();
}
Expand Down
7 changes: 6 additions & 1 deletion src/DependencyInjection/LazyContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
use Rector\StaticTypeMapper\PhpParser\StringNodeMapper;
use Rector\StaticTypeMapper\PhpParser\UnionTypeNodeMapper;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\Util\FileHasher;
use Rector\Utils\Command\MissingInSetCommand;
use Rector\Utils\Command\OutsideAnySetCommand;
use Symfony\Component\Console\Application;
Expand Down Expand Up @@ -469,7 +470,11 @@ static function (Container $container): DynamicSourceLocatorProvider {
$rectorConfig->afterResolving(
DynamicSourceLocatorProvider::class,
static function (DynamicSourceLocatorProvider $dynamicSourceLocatorProvider, Container $container): void {
$dynamicSourceLocatorProvider->autowire($container->make(ReflectionProvider::class));
$dynamicSourceLocatorProvider->autowire(
$container->make(ReflectionProvider::class),
$container->make(Cache::class),
$container->make(FileHasher::class)
);
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorFactory;
use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocator;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Caching\Cache;
use Rector\Caching\Enum\CacheKey;
use Rector\Contract\DependencyInjection\ResetableInterface;
use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment;
use Rector\Util\FileHasher;

/**
* @api phpstan external
Expand All @@ -36,22 +39,48 @@ final class DynamicSourceLocatorProvider implements ResetableInterface

private ReflectionProvider $reflectionProvider;

private Cache $cache;

private FileHasher $fileHasher;

public function __construct(
private readonly FileNodesFetcher $fileNodesFetcher,
private readonly OptimizedDirectorySourceLocatorFactory $optimizedDirectorySourceLocatorFactory
) {
}

public function autowire(ReflectionProvider $reflectionProvider): void
public function autowire(
ReflectionProvider $reflectionProvider,
Cache $cache,
FileHasher $fileHasher
): void
{
$this->reflectionProvider = $reflectionProvider;
$this->cache = $cache;
$this->fileHasher = $fileHasher;
}

public function setFilePath(string $filePath): void
{
$this->filePaths = [$filePath];
}

public function getCacheClassNameKey(): string
{
$paths = [];

foreach ($this->filePaths as $filePath) {
$paths[] = (string) realpath($filePath);
}

foreach ($this->directories as $directory) {
$paths[] = (string) realpath($directory);
}

$paths = array_filter($paths);
return CacheKey::CLASSNAMES_HASH_KEY . '_' . $this->fileHasher->hash((string) json_encode($paths));
}

/**
* @param string[] $files
*/
Expand Down Expand Up @@ -109,6 +138,19 @@ public function reset(): void
$this->aggregateSourceLocator = null;
}

/**
* @param class-string[] $classNamesCache
*/
private function locateCachedClassNames(array $classNamesCache): void
{
foreach ($classNamesCache as $classNameCache) {
try {
$this->reflectionProvider->getClass($classNameCache);
} catch (ClassNotFoundException) {
}
}
}

/**
* @param OptimizedSingleFileSourceLocator[]|NewOptimizedDirectorySourceLocator[] $sourceLocators
*/
Expand All @@ -123,19 +165,38 @@ private function collectClasses(AggregateSourceLocator $aggregateSourceLocator,
return;
}

$key = CacheKey::CLASSNAMES_HASH_KEY . '_' . $this->getCacheClassNameKey();
$classNamesCache = $this->cache->load($key, CacheKey::CLASSNAMES_HASH_KEY);

if (is_string($classNamesCache)) {
$classNamesCache = json_decode($classNamesCache);
if (is_array($classNamesCache)) {
$this->locateCachedClassNames($classNamesCache);
return;
}
}

$reflector = new DefaultReflector($aggregateSourceLocator);
$classNames = [];

// trigger collect "classes" on get class on locate identifier
try {
$reflections = $reflector->reflectAllClasses();
foreach ($reflections as $reflection) {
$className = $reflection->getName();

// make 'classes' collection
try {
$this->reflectionProvider->getClass($reflection->getName());
$this->reflectionProvider->getClass($className);
} catch (ClassNotFoundException) {
continue;
}

$classNames[] = $className;
}
} catch (CouldNotReadFileException) {
}

$this->cache->save($key, CacheKey::CLASSNAMES_HASH_KEY, json_encode($classNames));
}
}