Skip to content

Commit

Permalink
minor #28295 [VarExporter] optimize dumped code in time and space (ni…
Browse files Browse the repository at this point in the history
…colas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[VarExporter] optimize dumped code in time and space

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

Let's squeeze some more µs when running exported code.
On a simple case run 100k times with a few objects, I go from 1.8s to 1.5s.
The generated exports are also a bit smaller if it matters.

This works by:
- using local variables instead of manually dealing with a stack
- creating more optimized object hydrators for internal classes

This PR also fixes handling of hard references that are bound to external variables.

Commits
-------

07e90d7 [VarExporter] optimize dumped code in time and space
  • Loading branch information
fabpot committed Sep 4, 2018
2 parents a82b2a7 + 07e90d7 commit cabbf51
Show file tree
Hide file tree
Showing 20 changed files with 308 additions and 219 deletions.
172 changes: 108 additions & 64 deletions src/Symfony/Component/VarExporter/Internal/Exporter.php
Expand Up @@ -48,10 +48,11 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
$refs[$k] = $value = $values[$k];
if ($value instanceof Reference && 0 > $value->id) {
$valuesAreStatic = false;
++$value->count;
continue;
}
$refsPool[] = array(&$refs[$k], $value, &$value);
$refs[$k] = $values[$k] = new Reference(-\count($refsPool));
$refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value);
}

if (\is_array($value)) {
Expand Down Expand Up @@ -80,12 +81,13 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
Registry::getClassReflector($class);
serialize(Registry::$prototypes[$class]);
}
$reflector = Registry::$reflectors[$class];
$proto = Registry::$prototypes[$class];

if ($value instanceof \ArrayIterator || $value instanceof \ArrayObject) {
// ArrayIterator and ArrayObject need special care because their "flags"
// option changes the behavior of the (array) casting operator.
$proto = Registry::$cloneable[$class] ? clone Registry::$prototypes[$class] : Registry::$reflectors[$class]->newInstanceWithoutConstructor();
$proto = Registry::$cloneable[$class] ? clone Registry::$prototypes[$class] : $reflector->newInstanceWithoutConstructor();
$properties = self::getArrayObjectProperties($value, $arrayValue, $proto);
} elseif ($value instanceof \SplObjectStorage) {
// By implementing Serializable, SplObjectStorage breaks internal references,
Expand Down Expand Up @@ -116,20 +118,29 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
foreach ($arrayValue as $name => $v) {
$n = (string) $name;
if ('' === $n || "\0" !== $n[0]) {
$c = $class;
$c = '*';
$properties[$c][$n] = $v;
unset($sleep[$n]);
} elseif ('*' === $n[1]) {
$c = $class;
$n = substr($n, 3);
$c = $reflector->getProperty($n)->class;
if ('Error' === $c) {
$c = 'TypeError';
} elseif ('Exception' === $c) {
$c = 'ErrorException';
}
$properties[$c][$n] = $v;
unset($sleep[$n]);
} else {
$i = strpos($n, "\0", 2);
$c = substr($n, 1, $i - 1);
$n = substr($n, 1 + $i);
}
if (null === $sleep) {
$properties[$c][$n] = $v;
} elseif (isset($sleep[$n]) && $c === $class) {
$properties[$c][$n] = $v;
unset($sleep[$n]);
if (null === $sleep) {
$properties[$c][$n] = $v;
} elseif (isset($sleep[$n]) && $c === $class) {
$properties[$c][$n] = $v;
unset($sleep[$n]);
}
}
if (\array_key_exists($name, $proto) && $proto[$name] === $v) {
unset($properties[$c][$n]);
Expand Down Expand Up @@ -173,11 +184,14 @@ public static function export($value, $indent = '')

if ($value instanceof Reference) {
if (0 <= $value->id) {
return '\\'.Registry::class.'::$objects['.$value->id.']';
return '$o['.$value->id.']';
}
if (!$value->count) {
return self::export($value->value, $indent);
}
$value = -$value->id;

return '&\\'.Registry::class.'::$references['.$value.']';
return '&$r['.$value.']';
}
$subIndent = $indent.' ';

Expand Down Expand Up @@ -213,9 +227,11 @@ public static function export($value, $indent = '')
$code = '';
foreach ($value as $k => $v) {
$code .= $subIndent;
if ($k !== ++$j) {
if (!\is_int($k) || 1 !== $k - $j) {
$code .= self::export($k, $subIndent).' => ';
$j = INF;
}
if (\is_int($k)) {
$j = $k;
}
$code .= self::export($v, $subIndent).",\n";
}
Expand All @@ -224,82 +240,110 @@ public static function export($value, $indent = '')
}

if ($value instanceof Values) {
$code = '';
$code = $subIndent."\$r = [],\n";
foreach ($value->values as $k => $v) {
$code .= $subIndent.'\\'.Registry::class.'::$references['.$k.'] = '.self::export($v, $subIndent).",\n";
$code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n";
}

return "[\n".$code.$indent.']';
}

if ($value instanceof Registry) {
$code = '';
$reflectors = array();
$serializables = array();
return self::exportRegistry($value, $indent, $subIndent);
}

foreach ($value as $k => $class) {
if (':' === ($class[1] ?? null)) {
$serializables[$k] = $class;
continue;
}
$c = '\\'.$class.'::class';
$reflectors[$class] = '\\'.Registry::class.'::$reflectors['.$c.'] ?? \\'.Registry::class.'::getClassReflector('.$c.', '
.self::export(Registry::$instantiableWithoutConstructor[$class]).', '
.self::export(Registry::$cloneable[$class])
.')';
if ($value instanceof Hydrator) {
return self::exportHydrator($value, $indent, $subIndent);
}

throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', \is_object($value) ? \get_class($value) : \gettype($value)));
}

private static function exportRegistry(Registry $value, string $indent, string $subIndent): string
{
$code = '';
$reflectors = array();
$serializables = array();
$seen = array();
$prototypesAccess = 0;
$factoriesAccess = 0;
$r = '\\'.Registry::class;
$j = -1;

foreach ($value as $k => $class) {
if (':' === ($class[1] ?? null)) {
$serializables[$k] = $class;
continue;
}
$code .= $subIndent.(1 !== $k - $j ? $k.' => ' : '');
$j = $k;
$eol = ",\n";
$c = '[\\'.$class.'::class]';

if ($seen[$class] ?? false) {
if (Registry::$cloneable[$class]) {
$code .= $subIndent.'clone \\'.Registry::class.'::$prototypes['.$c."],\n";
} elseif (Registry::$instantiableWithoutConstructor[$class]) {
$code .= $subIndent.'\\'.Registry::class.'::$reflectors['.$c."]->newInstanceWithoutConstructor(),\n";
++$prototypesAccess;
$code .= 'clone $p'.$c;
} else {
$code .= $subIndent.'\\'.Registry::class.'::$reflectors['.$c."]->newInstance(),\n";
++$factoriesAccess;
$code .= '$f'.$c.'()';
}
}

if ($reflectors) {
$code = "[\n".$subIndent.implode(",\n".$subIndent, $reflectors).",\n".$indent."], [\n".$code.$indent.'], ';
$code .= !$serializables ? "[\n".$indent.']' : self::export($serializables, $indent);
} else {
$code = '[], []';
$code .= ', '.self::export($serializables, $indent);
$seen[$class] = true;
if (Registry::$cloneable[$class]) {
$code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p =& '.$r.'::$prototypes)').$c.' ?? '.$r.'::p';
} else {
$code .= '('.($factoriesAccess++ ? '$f' : '($f =& '.$r.'::$factories)').$c.' ?? '.$r.'::f';
$eol = '()'.$eol;
}
$code .= '('.substr($c, 1, -1).', '.self::export(Registry::$instantiableWithoutConstructor[$class]).'))';
}
$code .= $eol;
}

return '\\'.Registry::class.'::push('.$code.')';
if (1 === $prototypesAccess) {
$code = str_replace('($p =& '.$r.'::$prototypes)', $r.'::$prototypes', $code);
}
if (1 === $factoriesAccess) {
$code = str_replace('($f =& '.$r.'::$factories)', $r.'::$factories', $code);
}
if ('' !== $code) {
$code = "\n".$code.$indent;
}

if ($value instanceof Configurator) {
$code = '';
foreach ($value->properties as $class => $properties) {
$code .= $subIndent.' \\'.$class.'::class => '.self::export($properties, $subIndent.' ').",\n";
}
if ($serializables) {
$code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')';
} else {
$code = '['.$code.']';
}

$code = array(
self::export($value->registry, $subIndent),
self::export($value->values, $subIndent),
'' !== $code ? "[\n".$code.$subIndent.']' : '[]',
self::export($value->value, $subIndent),
self::export($value->wakeups, $subIndent),
);
return '$o = '.$code;
}

return '\\'.\get_class($value)."::pop(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')';
private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string
{
$code = '';
foreach ($value->properties as $class => $properties) {
$c = '*' !== $class ? '\\'.$class.'::class' : "'*'";
$code .= $subIndent.' '.$c.' => '.self::export($properties, $subIndent.' ').",\n";
}

throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', \is_object($value) ? \get_class($value) : \gettype($value)));
$code = array(
self::export($value->registry, $subIndent),
self::export($value->values, $subIndent),
'' !== $code ? "[\n".$code.$subIndent.']' : '[]',
self::export($value->value, $subIndent),
self::export($value->wakeups, $subIndent),
);

return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')';
}

/**
* Extracts the state of an ArrayIterator or ArrayObject instance.
*
* For performance this method is public and has no type-hints.
*
* @param \ArrayIterator|\ArrayObject $value
* @param array &$arrayValue
* @param object $proto
*
* @return array
* @param \ArrayIterator|\ArrayObject $proto
*/
public static function getArrayObjectProperties($value, &$arrayValue, $proto)
private static function getArrayObjectProperties($value, array &$arrayValue, $proto): array
{
$reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject';
$reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector);
Expand Down
Expand Up @@ -16,9 +16,9 @@
*
* @internal
*/
class Configurator
class Hydrator
{
public static $configurators = array();
public static $hydrators = array();

public $registry;
public $values;
Expand All @@ -35,12 +35,10 @@ public function __construct(?Registry $registry, ?Values $values, array $propert
$this->wakeups = $wakeups;
}

public static function pop($objects, $values, $properties, $value, $wakeups)
public static function hydrate($objects, $values, $properties, $value, $wakeups)
{
list(Registry::$objects, Registry::$references) = \array_pop(Registry::$stack);

foreach ($properties as $class => $vars) {
(self::$configurators[$class] ?? self::getConfigurator($class))($vars, $objects);
(self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects);
}
foreach ($wakeups as $i) {
$objects[$i]->__wakeup();
Expand All @@ -49,26 +47,30 @@ public static function pop($objects, $values, $properties, $value, $wakeups)
return $value;
}

public static function getConfigurator($class)
public static function getHydrator($class)
{
$classReflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);

if (!$classReflector->isInternal()) {
return self::$configurators[$class] = \Closure::bind(function ($properties, $objects) {
if ('*' === $class) {
return self::$hydrators[$class] = static function ($properties, $objects) {
foreach ($properties as $name => $values) {
foreach ($values as $i => $v) {
$objects[$i]->$name = $v;
}
}
}, null, $class);
};
}

$classReflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);

if (!$classReflector->isInternal()) {
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, $class);
}

switch ($class) {
case 'ArrayIterator':
case 'ArrayObject':
$constructor = $classReflector->getConstructor();

return self::$configurators[$class] = static function ($properties, $objects) use ($constructor) {
return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) {
foreach ($properties as $name => $values) {
if ("\0" !== $name) {
foreach ($values as $i => $v) {
Expand All @@ -81,8 +83,16 @@ public static function getConfigurator($class)
}
};

case 'ErrorException':
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, new class() extends \ErrorException {
});

case 'TypeError':
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, new class() extends \Error {
});

case 'SplObjectStorage':
return self::$configurators[$class] = static function ($properties, $objects) {
return self::$hydrators[$class] = static function ($properties, $objects) {
foreach ($properties as $name => $values) {
if ("\0" === $name) {
foreach ($values as $i => $v) {
Expand All @@ -100,23 +110,18 @@ public static function getConfigurator($class)
}

$propertyReflectors = array();
foreach ($classReflector->getProperties(\ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PRIVATE) as $propertyReflector) {
foreach ($classReflector->getProperties() as $propertyReflector) {
if (!$propertyReflector->isStatic()) {
$propertyReflector->setAccessible(true);
$propertyReflectors[$propertyReflector->name] = $propertyReflector;
}
}

return self::$configurators[$class] = static function ($properties, $objects) use ($propertyReflectors) {
return self::$hydrators[$class] = static function ($properties, $objects) use ($propertyReflectors) {
foreach ($properties as $name => $values) {
if (isset($propertyReflectors[$name])) {
foreach ($values as $i => $v) {
$propertyReflectors[$name]->setValue($objects[$i], $v);
}
} else {
foreach ($values as $i => $v) {
$objects[$i]->$name = $v;
}
$p = $propertyReflectors[$name];
foreach ($values as $i => $v) {
$p->setValue($objects[$i], $v);
}
}
};
Expand Down
5 changes: 4 additions & 1 deletion src/Symfony/Component/VarExporter/Internal/Reference.php
Expand Up @@ -19,9 +19,12 @@
class Reference
{
public $id;
public $value;
public $count = 0;

public function __construct(int $id)
public function __construct(int $id, $value = null)
{
$this->id = $id;
$this->value = $value;
}
}

0 comments on commit cabbf51

Please sign in to comment.