8000 minor #28295 [VarExporter] optimize dumped code in time and space (ni… · sroze/symfony@cabbf51 · GitHub
[go: up one dir, main page]

Skip to content

Commit cabbf51

Browse files
committed
minor symfony#28295 [VarExporter] optimize dumped code in time and space (nicolas-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
2 parents a82b2a7 + 07e90d7 commit cabbf51

20 files changed

+308
-219
lines changed

src/Symfony/Component/VarExporter/Internal/Exporter.php

Lines changed: 108 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
4848
$refs[$k] = $value = $values[$k];
4949
if ($value instanceof Reference && 0 > $value->id) {
5050
$valuesAreStatic = false;
51+
++$value->count;
5152
continue;
5253
}
5354
$refsPool[] = array(&$refs[$k], $value, &$value);
54-
$refs[$k] = $values[$k] = new Reference(-\count($refsPool));
55+
$refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value);
5556
}
5657

5758
if (\is_array($value)) {
@@ -80,12 +81,13 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
8081
Registry::getClassReflector($class);
8182
serialize(Registry::$prototypes[$class]);
8283
}
84+
$reflector = Registry: F438 :$reflectors[$class];
8385
$proto = Registry::$prototypes[$class];
8486

8587
if ($value instanceof \ArrayIterator || $value instanceof \ArrayObject) {
8688
// ArrayIterator and ArrayObject need special care because their "flags"
8789
// option changes the behavior of the (array) casting operator.
88-
$proto = Registry::$cloneable[$class] ? clone Registry::$prototypes[$class] : Registry::$reflectors[$class]->newInstanceWithoutConstructor();
90+
$proto = Registry::$cloneable[$class] ? clone Registry::$prototypes[$class] : $reflector->newInstanceWithoutConstructor();
8991
$properties = self::getArrayObjectProperties($value, $arrayValue, $proto);
9092
} elseif ($value instanceof \SplObjectStorage) {
9193
// By implementing Serializable, SplObjectStorage breaks internal references,
@@ -116,20 +118,29 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
116118
foreach ($arrayValue as $name => $v) {
117119
$n = (string) $name;
118120
if ('' === $n || "\0" !== $n[0]) {
119-
$c = $class;
121+
$c = '*';
122+
$properties[$c][$n] = $v;
123+
unset($sleep[$n]);
120124
} elseif ('*' === $n[1]) {
121-
$c = $class;
122125
$n = substr($n, 3);
126+
$c = $reflector->getProperty($n)->class;
127+
if ('Error' === $c) {
128+
$c = 'TypeError';
129+
} elseif ('Exception' === $c) {
130+
$c = 'ErrorException';
131+
}
132+
$properties[$c][$n] = $v;
133+
unset($sleep[$n]);
123134
} else {
124135
$i = strpos($n, "\0", 2);
125136
$c = substr($n, 1, $i - 1);
126137
$n = substr($n, 1 + $i);
127-
}
128-
if (null === $sleep) {
129-
$properties[$c][$n] = $v;
130-
} elseif (isset($sleep[$n]) && $c === $class) {
131-
$properties[$c][$n] = $v;
132-
unset($sleep[$n]);
138+
if (null === $sleep) {
139+
$properties[$c][$n] = $v;
140+
} elseif (isset($sleep[$n]) && $c === $class) {
141+
$properties[$c][$n] = $v;
142+
unset($sleep[$n]);
143+
}
133144
}
134145
if (\array_key_exists($name, $proto) && $proto[$name] === $v) {
135146
unset($properties[$c][$n]);
@@ -173,11 +184,14 @@ public static function export($value, $indent = '')
173184

174185
if ($value instanceof Reference) {
175186
if (0 <= $value->id) {
176-
return '\\'.Registry::class.'::$objects['.$value->id.']';
187+
return '$o['.$value->id.']';
188+
}
189+
if (!$value->count) {
190+
return self::export($value->value, $indent);
177191
}
178192
$value = -$value->id;
179193

180-
return '&\\'.Registry::class.'::$references['.$value.']';
194+
return '&$r['.$value.']';
181195
}
182196
$subIndent = $indent.' ';
183197

@@ -213,9 +227,11 @@ public static function export($value, $indent = '')
213227
$code = '';
214228
foreach ($value as $k => $v) {
215229
$code .= $subIndent;
216-
if ($k !== ++$j) {
230+
if (!\is_int($k) || 1 !== $k - $j) {
217231
$code .= self::export($k, $subIndent).' => ';
218-
$j = INF;
232+
}
233+
if (\is_int($k)) {
234+
$j = $k;
219235
}
220236
$code .= self::export($v, $subIndent).",\n";
221237
}
@@ -224,82 +240,110 @@ public static function export($value, $indent = '')
224240
}
225241

226242
if ($value instanceof Values) {
227-
$code = '';
243+
$code = $subIndent."\$r = [],\n";
228244
foreach ($value->values as $k => $v) {
229-
$code .= $subIndent.'\\'.Registry::class.'::$references['.$k.'] = '.self::export($v, $subIndent).",\n";
245+
$code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n";
230246
}
231247

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

235251
if ($value instanceof Registry) {
236-
$code = '';
237-
$reflectors = array();
238-
$serializables = array();
252+
return self::exportRegistry($value, $indent, $subIndent);
253+
}
239254

240-
foreach ($value as $k => $class) {
241-
if (':' === ($class[1] ?? null)) {
242-
$serializables[$k] = $class;
243-
continue;
244-
}
245-
$c = '\\'.$class.'::class';
246-
$reflectors[$class] = '\\'.Registry::class.'::$reflectors['.$c.'] ?? \\'.Registry::class.'::getClassReflector('.$c.', '
247-
.self::export(Registry::$instantiableWithoutConstructor[$class]).', '
248-
.self::export(Registry::$cloneable[$class])
249-
.')';
255+
if ($value instanceof Hydrator) {
256+
return self::exportHydrator($value, $indent, $subIndent);
257+
}
258+
259+
throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', \is_object($value) ? \get_class($value) : \gettype($value)));
260+
}
261+
262+
private static function exportRegistry(Registry $value, string $indent, string $subIndent): string
263+
{
264+
$code = '';
265+
$reflectors = array();
266+
$serializables = array();
267+
$seen = array();
268+
$prototypesAccess = 0;
269+
$factoriesAccess = 0;
270+
$r = '\\'.Registry::class;
271+
$j = -1;
272+
273+
foreach ($value as $k => $class) {
274+
if (':' === ($class[1] ?? null)) {
275+
$serializables[$k] = $class;
276+
continue;
277+
}
278+
$code .= $subIndent.(1 !== $k - $j ? $k.' => ' : '');
279+
$j = $k;
280+
$eol = ",\n";
281+
$c = '[\\'.$class.'::class]';
250282

283+
if ($seen[$class] ?? false) {
251284
if (Registry::$cloneable[$class]) {
252-
$code .= $subIndent.'clone \\'.Registry::class.'::$prototypes['.$c."],\n";
253-
} elseif (Registry::$instantiableWithoutConstructor[$class]) {
254-
$code .= $subIndent.'\\'.Registry::class.'::$reflectors['.$c."]->newInstanceWithoutConstructor(),\n";
285+
++$prototypesAccess;
286+
$code .= 'clone $p'.$c;
255287
} else {
256-
$code .= $subIndent.'\\'.Registry::class.'::$reflectors['.$c."]->newInstance(),\n";
288+
++$factoriesAccess;
289+
$code .= '$f'.$c.'()';
257290
}
258-
}
259-
260-
if ($reflectors) {
261-
$code = "[\n".$subIndent.implode(",\n".$subIndent, $reflectors).",\n".$indent."], [\n".$code.$indent.'], ';
262-
$code .= !$serializables ? "[\n".$indent.']' : self::export($serializables, $indent);
263291
} else {
264-
$code = '[], []';
265-
$code .= ', '.self::export($serializables, $indent);
292+
$seen[$class] = true;
293+
if (Registry::$cloneable[$class]) {
294+
$code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p =& '.$r.'::$prototypes)').$c.' ?? '.$r.'::p';
295+
} else {
296+
$code .= '('.($factoriesAccess++ ? '$f' : '($f =& '.$r.'::$factories)').$c.' ?? '.$r.'::f';
297+
$eol = '()'.$eol;
298+
}
299+
$code .= '('.substr($c, 1, -1).', '.self::export(Registry::$instantiableWithoutConstructor[$class]).'))';
266300
}
301+
$code .= $eol;
302+
}
267303

268-
return '\\'.Registry::class.'::push('.$code.')';
304+
if (1 === $prototypesAccess) {
305+
$code = str_replace('($p =& '.$r.'::$prototypes)', $r.'::$prototypes', $code);
306+
}
307+
if (1 === $factoriesAccess) {
308+
$code = str_replace('($f =& '.$r.'::$factories)', $r.'::$factories', $code);
309+
}
310+
if ('' !== $code) {
311+
$code = "\n".$code.$indent;
269312
}
270313

271-
if ($value instanceof Configurator) {
272-
$code = '';
273-
foreach ($value->properties as $class => $properties) {
274-
$code .= $subIndent.' \\'.$class.'::class => '.self::export($properties, $subIndent.' ').",\n";
275-
}
314+
if ($serializables) {
315+
$code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')';
316+
} else {
317+
$code = '['.$code.']';
318+
}
276319

277-
$code = array(
278-
self::export($value->registry, $subIndent),
279-
self::export($value->values, $subIndent),
280-
'' !== $code ? "[\n".$code.$subIndent.']' : '[]',
281-
self::export($value->value, $subIndent),
282-
self::export($value->wakeups, $subIndent),
283-
);
320+
return '$o = '.$code;
321+
}
284322

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

288-
throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', \is_object($value) ? \get_class($value) : \gettype($value)));
331+
$code = array(
332+
self::export($value->registry, $subIndent),
333+
self::export($value->values, $subIndent),
334+
'' !== $code ? "[\n".$code.$subIndent.']' : '[]',
335+
self::export($value->value, $subIndent),
336+
self::export($value->wakeups, $subIndent),
337+
);
338+
339+
return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')';
289340
}
290341

291342
/**
292-
* Extracts the state of an ArrayIterator or ArrayObject instance.
293-
*
294-
* For performance this method is public and has no type-hints.
295-
*
296343
* @param \ArrayIterator|\ArrayObject $value
297-
* @param array &$arrayValue
298-
* @param object $proto
299-
*
300-
* @return array
344+
* @param \ArrayIterator|\ArrayObject $proto
301345
*/
302-
public static function getArrayObjectProperties($value, &$arrayValue, $proto)
346+
private static function getArrayObjectProperties($value, array &$arrayValue, $proto): array
303347
{
304348
$reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject';
305349
$reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector);

src/Symfony/Component/VarExporter/Internal/Configurator.php renamed to src/Symfony/Component/VarExporter/Internal/Hydrator.php

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
*
1717
* @internal
1818
*/
19-
class Configurator
19+
class Hydrator
2020
{
21-
public static $configurators = array();
21+
public static $hydrators = array();
2222

2323
public $registry;
2424
public $values;
@@ -35,12 +35,10 @@ public function __construct(?Registry $registry, ?Values $values, array $propert
3535
$this->wakeups = $wakeups;
3636
}
3737

38-
public static function pop($objects, $values, $properties, $value, $wakeups)
38+
public static function hydrate($objects, $values, $properties, $value, $wakeups)
3939
{
40-
list(Registry::$objects, Registry::$references) = \array_pop(Registry::$stack);
41-
4240
foreach ($properties as $class => $vars) {
43-
(self::$configurators[$class] ?? self::getConfigurator($class))($vars, $objects);
41+
(self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects);
4442
}
4543
foreach ($wakeups as $i) {
4644
$objects[$i]->__wakeup();
@@ -49,26 +47,30 @@ public static function pop($objects, $values, $properties, $value, $wakeups)
4947
return $value;
5048
}
5149

52-
public static function getConfigurator($class)
50+
public static function getHydrator($class)
5351
{
54-
$classReflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
55-
56-
if (!$classReflector->isInternal()) {
57-
return self::$configurators[$class] = \Closure::bind(function ($properties, $objects) {
52+
if ('*' === $class) {
53+
return self::$hydrators[$class] = static function ($properties, $objects) {
5854
foreach ($properties as $name => $values) {
5955
foreach ($values as $i => $v) {
6056
$objects[$i]->$name = $v;
6157
}
6258
}
63-
}, null, $class);
59+
};
60+
}
61+
62+
$classReflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
63+
64+
if (!$classReflector->isInternal()) {
65+
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, $class);
6466
}
6567

6668
switch ($class) {
6769
case 'ArrayIterator':
6870
case 'ArrayObject':
6971
$constructor = $classReflector->getConstructor();
7072

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

86+
case 'ErrorException':
87+
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, new class() extends \ErrorException {
88+
});
89+
90+
case 'TypeError':
91+
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, new class() extends \Error {
92+
});
93+
8494
case 'SplObjectStorage':
85-
return self::$configurators[$class] = static function ($properties, $objects) {
95+
return self::$hydrators[$class] = static function ($properties, $objects) {
8696
foreach ($properties as $name => $values) {
8797
if ("\0" === $name) {
8898
foreach ($values as $i => $v) {
@@ -100,23 +110,18 @@ public static function getConfigurator($class)
100110
}
101111

102112
$propertyReflectors = array();
103-
foreach ($classReflector->getProperties(\ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PRIVATE) as $propertyReflector) {
113+
foreach ($classReflector->getProperties() as $propertyReflector) {
104114
if (!$propertyReflector->isStatic()) {
105115
$propertyReflector->setAccessible(true);
106116
$propertyReflectors[$propertyReflector->name] = $propertyReflector;
107117
}
108118
}
109119

110-
return self::$configurators[$class] = static function ($properties, $objects) use ($propertyReflectors) {
120+
return self::$hydrators[$class] = static function ($properties, $objects) use ($propertyReflectors) {
111121
foreach ($properties as $name => $values) {
112-
if (isset($propertyReflectors[$name])) {
113-
foreach ($values as $i => $v) {
114-
$propertyReflectors[$name]->setValue($objects[$i], $v);
115-
}
116-
} else {
117-
foreach ($values as $i => $v) {
118-
$objects[$i]->$name = $v;
119-
}
122+
$p = $propertyReflectors[$name];
123+
foreach ($values as $i => $v) {
124+
$p->setValue($objects[$i], $v);
120125
}
121126
}
122127
};

src/Symfony/Component/VarExporter/Internal/Reference.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
class Reference
2020
{
2121
public $id;
22+
public $value;
23+
public $count = 0;
2224

23-
public function __construct(int $id)
25+
public function __construct(int $id, $value = null)
2426
{
2527
$this->id = $id;
28+
$this->value = $value;
2629
}
2730
}

0 commit comments

Comments
 (0)
0