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

perf: 优化hyperf启动耗时, 元数据分类缓存, 有效缩短扫描时间 #644

Merged
merged 6 commits into from Oct 3, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -79,6 +79,10 @@ Now:
- [#612](https://github.com/hyperf-cloud/hyperf/pull/612) Deleted useless `$url` for RingPHP Handlers.
- [#616](https://github.com/hyperf-cloud/hyperf/pull/616) [#618](https://github.com/hyperf-cloud/hyperf/pull/618) Deleted useless code of guzzle.

## Optimized

- [#644](https://github.com/hyperf-cloud/hyperf/pull/644) Optimized annotation scan process, separate to two scan parts `app` and `vendor`, greatly decrease the elapsed time.

## Fixed

- [#448](https://github.com/hyperf-cloud/hyperf/pull/448) Fixed TCP Server does not works when HTTP Server or WebSocket Server exists.
Expand Down
17 changes: 10 additions & 7 deletions src/di/src/Annotation/Scanner.php
Expand Up @@ -15,7 +15,6 @@
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Hyperf\Di\Aop\Ast;
use Hyperf\Di\Aop\AstCollector;
use Hyperf\Di\ReflectionManager;
use Symfony\Component\Finder\Finder;

Expand Down Expand Up @@ -53,21 +52,27 @@ public function scan(array $paths): array
array_walk($this->ignoreAnnotations, function ($value) {
AnnotationReader::addGlobalIgnoredName($value);
});
$reader = new AnnotationReader();
$classCollection = [];
$meta = [];
foreach ($finder as $file) {
try {
$stmts = $this->parser->parse($file->getContents());
$className = $this->parser->parseClassByStmts($stmts);
if (! $className) {
continue;
}
AstCollector::set($className, $stmts);
$classCollection[] = $className;
$meta[$className] = $stmts;
} catch (\RuntimeException $e) {
continue;
}
}
$this->collect(array_keys($meta));

return $meta;
}

public function collect($classCollection)
{
$reader = new AnnotationReader();
// Because the annotation class should loaded before use it, so load file via $finder previous, and then parse annotation here.
foreach ($classCollection as $className) {
$reflectionClass = ReflectionManager::reflectClass($className);
Expand Down Expand Up @@ -106,8 +111,6 @@ public function scan(array $paths): array
}
}
}

return $classCollection;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/di/src/Command/InitProxyCommand.php
Expand Up @@ -96,7 +96,8 @@ private function createAopProxies()
$this->clearRuntime($runtime);
}

$classCollection = $this->scanner->scan($scanDirs);
$meta = $this->scanner->scan($scanDirs);
$classCollection = array_keys($meta);

foreach ($classCollection as $item) {
try {
Expand Down
70 changes: 54 additions & 16 deletions src/di/src/Definition/DefinitionSource.php
Expand Up @@ -16,15 +16,15 @@
use Hyperf\Di\Annotation\AspectCollector;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Di\Annotation\Scanner;
use Hyperf\Di\MetadataCacheCollector;
use Hyperf\Di\Aop\AstCollector;
use Hyperf\Di\ReflectionManager;
use Hyperf\Utils\Str;
use ReflectionClass;
use ReflectionFunctionAbstract;
use Symfony\Component\Finder\Finder;
use function class_exists;
use function count;
use function explode;
use function fclose;
use function feof;
use function fgets;
use function file_exists;
Expand Down Expand Up @@ -56,7 +56,7 @@ class DefinitionSource implements DefinitionSourceInterface
*
* @var string
*/
private $cachePath = BASE_PATH . '/runtime/container/annotations.cache';
private $cachePath = BASE_PATH . '/runtime/container/annotations';

/**
* @var array
Expand Down Expand Up @@ -215,36 +215,75 @@ private function autowire(string $name, ObjectDefinition $definition = null): ?O
}

private function scan(array $paths, array $collectors): bool
{
$appPaths = $vendorPaths = [];

/**
* If you are a hyperf developer
* this value will be your local path, like hyperf/src.
* @var string
*/
$ident = 'vendor';
$isDefinedBasePath = defined('BASE_PATH');

foreach ($paths as $path) {
if ($isDefinedBasePath) {
if (Str::startsWith($path, BASE_PATH . '/' . $ident)) {
$vendorPaths[] = $path;
} else {
$appPaths[] = $path;
}
} else {
if (strpos($path, $ident) !== false) {
$vendorPaths[] = $path;
} else {
$appPaths[] = $path;
}
}
}

$this->loadMetadata($appPaths, 'app');
$this->loadMetadata($vendorPaths, 'vendor');

return true;
}

private function loadMetadata(array $paths, $type)
{
if (empty($paths)) {
return true;
}
$cachePath = $this->cachePath . '.' . $type . '.cache';
$pathsHash = md5(implode(',', $paths));
$cacher = new MetadataCacheCollector($collectors);
if ($this->hasAvailableCache($paths, $pathsHash, $this->cachePath)) {
$this->printLn('Detected an available cache, skip the scan process.');
[, $serialized] = explode(PHP_EOL, file_get_contents($this->cachePath));
$cacher->unserialize($serialized);
if ($this->hasAvailableCache($paths, $pathsHash, $cachePath)) {
$this->printLn('Detected an available cache, skip the ' . $type . ' scan process.');
[, $serialized] = explode(PHP_EOL, file_get_contents($cachePath));
$this->scanner->collect(unserialize($serialized));
return false;
}
$this->printLn('Scanning ...');
$this->scanner->scan($paths);
$this->printLn('Scan completed.');
$this->printLn('Scanning ' . $type . ' ...');
$startTime = microtime(true);
$meta = $this->scanner->scan($paths);
foreach ($meta as $className => $stmts) {
AstCollector::set($className, $stmts);
}
$useTime = microtime(true) - $startTime;
$this->printLn('Scan ' . $type . ' completed, took ' . $useTime * 1000 . ' milliseconds.');
if (! $this->enableCache) {
return true;
}
// enableCache: set cache
if (! file_exists($this->cachePath)) {
$exploded = explode('/', $this->cachePath);
if (! file_exists($cachePath)) {
$exploded = explode('/', $cachePath);
unset($exploded[count($exploded) - 1]);
$dirPath = implode('/', $exploded);
if (! is_dir($dirPath)) {
mkdir($dirPath, 0755, true);
}
}

$data = implode(PHP_EOL, [$pathsHash, $cacher->serialize()]);
file_put_contents($this->cachePath, $data);
$data = implode(PHP_EOL, [$pathsHash, serialize(array_keys($meta))]);
file_put_contents($cachePath, $data);
return true;
}

Expand All @@ -264,7 +303,6 @@ private function hasAvailableCache(array $paths, string $pathsHash, string $file
}
break;
}
fclose($handler);
$cacheLastModified = filemtime($filename) ?? 0;
$finder = new Finder();
$finder->files()->in($paths)->name('*.php');
Expand Down