Permalink
Browse files

feature #27471 [DI] Improve performance of removing/inlining passes (…

…nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[DI] Improve performance of removing/inlining passes

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #27386
| License       | MIT
| Doc PR        | -

Here is an optimization to reclaim some compilation time by optimizing the analysis of unused and inlined services.

This PR removes any use case for `RepeatedPass`, instead:
- `RemoveUnusedDefinitionsPass` works in one run, removing all private services that are unreachable from public services
-  `InlineServiceDefinitionsPass` reduces the number of nodes to analyze per iteration using `AnalyzeServiceReferencesPass` on a duplicated container internally.

https://blackfire.io/profiles/compare/00723822-6c09-431c-b98d-4a4197d044fc/graph?settings%5Bdimension%5D=wt&settings%5Bdisplay%5D=focused&settings%5BtabPane%5D=nodes&selected=Symfony%5CComponent%5CDependencyInjection%5CCompiler%5CRepeatedPass%3A%3Aprocess&callname=Symfony%5CComponent%5CDependencyInjection%5CCompiler%5CRepeatedPass%3A%3Aprocess

![image](https://user-images.githubusercontent.com/243674/40884496-c31e780e-6714-11e8-8218-967c4b25b9ce.png)

Commits
-------

cf375e5 [DI] Improve performance of removing/inlining passes
  • Loading branch information...
Tobion committed Jun 5, 2018
2 parents 4cd6477 + cf375e5 commit d8739d183e2e24f7da5741658bf22bf55eb9f0af
@@ -55,7 +55,12 @@ public function testPoolRefsAreWeak()
$container->setAlias('clearer_alias', 'clearer');
$pass = new RemoveUnusedDefinitionsPass();
$pass->setRepeatedPass(new RepeatedPass(array($pass)));
foreach ($container->getCompiler()->getPassConfig()->getRemovingPasses() as $removingPass) {
if ($removingPass instanceof RepeatedPass) {
$pass->setRepeatedPass(new RepeatedPass(array($pass)));
break;
}
}
foreach (array(new CachePoolPass(), $pass, new CachePoolClearerPass()) as $pass) {
$pass->process($container);
}
@@ -15,7 +15,9 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* @author Nicolas Grekas <p@tchwork.com>
@@ -28,6 +30,9 @@
protected $container;
protected $currentId;
private $processExpressions = false;
private $expressionLanguage;
/**
* {@inheritdoc}
*/
@@ -42,11 +47,17 @@ public function process(ContainerBuilder $container)
}
}
protected function enableExpressionProcessing()
{
$this->processExpressions = true;
}
/**
* Processes a value found in a definition tree.
*
* @param mixed $value
* @param bool $isRoot
* @param bool $inExpression
*
* @return mixed The processed value
*/
@@ -63,6 +74,8 @@ protected function processValue($value, $isRoot = false)
}
} elseif ($value instanceof ArgumentInterface) {
$value->setValues($this->processValue($value->getValues()));
} elseif ($value instanceof Expression && $this->processExpressions) {
$this->getExpressionLanguage()->compile((string) $value, array('this' => 'container'));
} elseif ($value instanceof Definition) {
$value->setArguments($this->processValue($value->getArguments()));
$value->setProperties($this->processValue($value->getProperties()));
@@ -169,4 +182,29 @@ protected function getReflectionMethod(Definition $definition, $method)
return $r;
}
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists(ExpressionLanguage::class)) {
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$providers = $this->container->getExpressionLanguageProviders();
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
if ('""' === substr_replace($arg, '', 1, -1)) {
$id = stripcslashes(substr($arg, 1, -1));
$arg = $this->processValue(new Reference($id), false, true);
if (!$arg instanceof Reference) {
throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, %s returned for service("%s").', get_class($this), is_object($arg) ? get_class($arg) : gettype($arg)));
}
$arg = sprintf('"%s"', $arg);
}
return sprintf('$this->get(%s)', $arg);
});
}
return $this->expressionLanguage;
}
}
@@ -14,11 +14,8 @@
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* Run this pass before passes that need to know more about the relation of
@@ -28,29 +25,32 @@
* retrieve the graph in other passes from the compiler.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements RepeatablePassInterface
{
private $graph;
private $currentDefinition;
private $onlyConstructorArguments;
private $lazy;
private $expressionLanguage;
private $definitions;
private $aliases;
/**
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
*/
public function __construct(bool $onlyConstructorArguments = false)
{
$this->onlyConstructorArguments = $onlyConstructorArguments;
$this->enableExpressionProcessing();
}
/**
* {@inheritdoc}
*/
public function setRepeatedPass(RepeatedPass $repeatedPass)
{
// no-op for BC
@trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
}
/**
@@ -62,16 +62,22 @@ public function process(ContainerBuilder $container)
$this->graph = $container->getCompiler()->getServiceReferenceGraph();
$this->graph->clear();
$this->lazy = false;
$this->definitions = $container->getDefinitions();
$this->aliases = $container->getAliases();
foreach ($container->getAliases() as $id => $alias) {
foreach ($this->aliases as $id => $alias) {
$targetId = $this->getDefinitionId((string) $alias);
$this->graph->connect($id, $alias, $targetId, $this->getDefinition($targetId), null);
$this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null);
}
parent::process($container);
try {
parent::process($container);
} finally {
$this->aliases = $this->definitions = array();
}
}
protected function processValue($value, $isRoot = false)
protected function processValue($value, $isRoot = false, bool $inExpression = false)
{
$lazy = $this->lazy;
@@ -82,14 +88,9 @@ protected function processValue($value, $isRoot = false)
return $value;
}
if ($value instanceof Expression) {
$this->getExpressionLanguage()->compile((string) $value, array('this' => 'container'));
return $value;
}
if ($value instanceof Reference) {
$targetId = $this->getDefinitionId((string) $value);
$targetDefinition = $this->getDefinition($targetId);
$targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null;
$this->graph->connect(
$this->currentId,
@@ -101,6 +102,18 @@ protected function processValue($value, $isRoot = false)
ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()
);
if ($inExpression) {
$this->graph->connect(
'.internal.reference_in_expression',
null,
$targetId,
$targetDefinition,
$value,
$this->lazy || ($targetDefinition && $targetDefinition->isLazy()),
true
);
}
return $value;
}
if (!$value instanceof Definition) {
@@ -127,49 +140,12 @@ protected function processValue($value, $isRoot = false)
return $value;
}
private function getDefinition(?string $id): ?Definition
{
return null === $id ? null : $this->container->getDefinition($id);
}
private function getDefinitionId(string $id): ?string
{
while ($this->container->hasAlias($id)) {
$id = (string) $this->container->getAlias($id);
}
if (!$this->container->hasDefinition($id)) {
return null;
}
return $id;
}
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists(ExpressionLanguage::class)) {
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$providers = $this->container->getExpressionLanguageProviders();
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
if ('""' === substr_replace($arg, '', 1, -1)) {
$id = stripcslashes(substr($arg, 1, -1));
$id = $this->getDefinitionId($id);
$this->graph->connect(
$this->currentId,
$this->currentDefinition,
$id,
$this->getDefinition($id)
);
}
return sprintf('$this->get(%s)', $arg);
});
while (isset($this->aliases[$id])) {
$id = (string) $this->aliases[$id];
}
return $this->expressionLanguage;
return isset($this->definitions[$id]) ? $id : null;
}
}
Oops, something went wrong.

0 comments on commit d8739d1

Please sign in to comment.