Skip to content

Commit 21d98d4

Browse files
committed
bug symfony#23644 [VarDumper] Dont use Stub objects for arrays - lower GC pressure (nicolas-grekas)
This PR was merged into the 3.3 branch. Discussion ---------- [VarDumper] Dont use Stub objects for arrays - lower GC pressure | Q | A | ------------- | --- | Branch? | 3.3 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#23644 | License | MIT | Doc PR | - Several recent profiles have shown that VarDumper triggers the garbage collector quite often, leading to high CPU usage. The reason for this is that internally, VarCloner creates one Stub object per array+object+resource. This PR removes the need for Stub objects for each arrays, replacing them with stub arrays. This should almost remove the GC pressure, since the number of Stub objects now has the same magnitude than the number of dumped objects. Meanwhile, this PR removes any use of the `symfony_debug` extension, which is mostly useless anyway. This helps make the code simpler (really :) ), thus helps maintenance (eg merging up to master.) I also changed the values of the constants defined in the Stub class, and removed the corresponding Data::mapStubConsts() method. Since the serialized format has changed (and we have to do it as there is no other way to fix this GC issue), there is no need to keep any sort of compat mapping there. Commits ------- 0d5012d [VarDumper] Dont use Stub objects for arrays
2 parents d569476 + 0d5012d commit 21d98d4

File tree

5 files changed

+160
-239
lines changed

5 files changed

+160
-239
lines changed

src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,16 +213,14 @@ public function cloneVar($var, $filter = 0)
213213
gc_disable();
214214
}
215215
try {
216-
$data = $this->doClone($var);
216+
return new Data($this->doClone($var));
217217
} finally {
218218
if ($gc) {
219219
gc_enable();
220220
}
221221
restore_error_handler();
222222
$this->prevErrorHandler = null;
223223
}
224-
225-
return new Data($data);
226224
}
227225

228226
/**

src/Symfony/Component/VarDumper/Cloner/Data.php

Lines changed: 24 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
/**
1717
* @author Nicolas Grekas <p@tchwork.com>
1818
*/
19-
class Data implements \ArrayAccess, \Countable, \IteratorAggregate, \Serializable
19+
class Data implements \ArrayAccess, \Countable, \IteratorAggregate
2020
{
2121
private $data;
2222
private $position = 0;
@@ -72,7 +72,7 @@ public function getValue($recursive = false)
7272
if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
7373
$item = $item->value;
7474
}
75-
if (!$item instanceof Stub) {
75+
if (!($item = $this->getStub($item)) instanceof Stub) {
7676
return $item;
7777
}
7878
if (Stub::TYPE_STRING === $item->type) {
@@ -82,20 +82,20 @@ public function getValue($recursive = false)
8282
$children = $item->position ? $this->data[$item->position] : array();
8383

8484
foreach ($children as $k => $v) {
85-
if ($recursive && !$v instanceof Stub) {
85+
if ($recursive && !($v = $this->getStub($v)) instanceof Stub) {
8686
continue;
8787
}
8888
$children[$k] = clone $this;
8989
$children[$k]->key = $k;
9090
$children[$k]->position = $item->position;
9191

9292
if ($recursive) {
93-
if ($v instanceof Stub && Stub::TYPE_REF === $v->type && $v->value instanceof Stub) {
93+
if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) {
9494
$recursive = (array) $recursive;
95-
if (isset($recursive[$v->value->position])) {
95+
if (isset($recursive[$v->position])) {
9696
continue;
9797
}
98-
$recursive[$v->value->position] = true;
98+
$recursive[$v->position] = true;
9999
}
100100
$children[$k] = $children[$k]->getValue($recursive);
101101
}
@@ -123,7 +123,7 @@ public function getIterator()
123123
public function __get($key)
124124
{
125125
if (null !== $data = $this->seek($key)) {
126-
$item = $data->data[$data->position][$data->key];
126+
$item = $this->getStub($data->data[$data->position][$data->key]);
127127

128128
return $item instanceof Stub || array() === $item ? $data : $item;
129129
}
@@ -236,7 +236,7 @@ public function seek($key)
236236
if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
237237
$item = $item->value;
238238
}
239-
if (!$item instanceof Stub || !$item->position) {
239+
if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) {
240240
return;
241241
}
242242
$keys = array($key);
@@ -278,57 +278,6 @@ public function dump(DumperInterface $dumper)
278278
$this->dumpItem($dumper, new Cursor(), $refs, $this->data[$this->position][$this->key]);
279279
}
280280

281-
/**
282-
* @internal
283-
*/
284-
public function serialize()
285-
{
286-
$data = $this->data;
287-
288-
foreach ($data as $i => $values) {
289-
foreach ($values as $k => $v) {
290-
if ($v instanceof Stub) {
291-
if (Stub::TYPE_ARRAY === $v->type) {
292-
$v = self::mapStubConsts($v, false);
293-
$data[$i][$k] = array($v->class, $v->position, $v->cut);
294-
} else {
295-
$v = self::mapStubConsts($v, false);
296-
$data[$i][$k] = array($v->class, $v->position, $v->cut, $v->type, $v->value, $v->handle, $v->refCount, $v->attr);
297-
}
298-
}
299-
}
300-
}
301-
302-
return serialize(array($data, $this->position, $this->key, $this->maxDepth, $this->maxItemsPerDepth, $this->useRefHandles));
303-
}
304-
305-
/**
306-
* @internal
307-
*/
308-
public function unserialize($serialized)
309-
{
310-
list($data, $this->position, $this->key, $this->maxDepth, $this->maxItemsPerDepth, $this->useRefHandles) = unserialize($serialized);
311-
312-
foreach ($data as $i => $values) {
313-
foreach ($values as $k => $v) {
314-
if ($v && is_array($v)) {
315-
$s = new Stub();
316-
if (3 === count($v)) {
317-
$s->type = Stub::TYPE_ARRAY;
318-
$s = self::mapStubConsts($s, false);
319-
list($s->class, $s->position, $s->cut) = $v;
320-
$s->value = $s->cut + count($data[$s->position]);
321-
} else {
322-
list($s->class, $s->position, $s->cut, $s->type, $s->value, $s->handle, $s->refCount, $s->attr) = $v;
323-
}
324-
$data[$i][$k] = self::mapStubConsts($s, true);
325-
}
326-
}
327-
}
328-
329-
$this->data = $data;
330-
}
331-
332281
/**
333282
* Depth-first dumping of items.
334283
*
@@ -346,7 +295,10 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
346295

347296
if (!$item instanceof Stub) {
348297
$cursor->attr = array();
349-
$type = gettype($item);
298+
$type = \gettype($item);
299+
if ($item && 'array' === $type) {
300+
$item = $this->getStub($item);
301+
}
350302
} elseif (Stub::TYPE_REF === $item->type) {
351303
if ($item->handle) {
352304
if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) {
@@ -360,7 +312,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
360312
}
361313
$cursor->attr = $item->attr;
362314
$type = $item->class ?: gettype($item->value);
363-
$item = $item->value;
315+
$item = $this->getStub($item->value);
364316
}
365317
if ($item instanceof Stub) {
366318
if ($item->refCount) {
@@ -458,21 +410,20 @@ private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCu
458410
return $hashCut;
459411
}
460412

461-
private static function mapStubConsts(Stub $stub, $resolve)
413+
private function getStub($item)
462414
{
463-
static $stubConstIndexes, $stubConstValues;
464-
465-
if (null === $stubConstIndexes) {
466-
$r = new \ReflectionClass(Stub::class);
467-
$stubConstIndexes = array_flip(array_values($r->getConstants()));
468-
$stubConstValues = array_flip($stubConstIndexes);
415+
if (!$item || !\is_array($item)) {
416+
return $item;
469417
}
470418

471-
$map = $resolve ? $stubConstValues : $stubConstIndexes;
472-
473-
$stub = clone $stub;
474-
$stub->type = $map[$stub->type];
475-
$stub->class = isset($map[$stub->class]) ? $map[$stub->class] : $stub->class;
419+
$stub = new Stub();
420+
$stub->type = Stub::TYPE_ARRAY;
421+
foreach ($item as $stub->class => $stub->position) {
422+
}
423+
if (isset($item[0])) {
424+
$stub->cut = $item[0];
425+
}
426+
$stub->value = $stub->cut + \count($this->data[$stub->position]);
476427

477428
return $stub;
478429
}

src/Symfony/Component/VarDumper/Cloner/Stub.php

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@
1616
*
1717
* @author Nicolas Grekas <p@tchwork.com>
1818
*/
19-
class Stub
19+
class Stub implements \Serializable
2020
{
21-
const TYPE_REF = 'ref';
22-
const TYPE_STRING = 'string';
23-
const TYPE_ARRAY = 'array';
24-
const TYPE_OBJECT = 'object';
25-
const TYPE_RESOURCE = 'resource';
21+
const TYPE_REF = 1;
22+
const TYPE_STRING = 2;
23+
const TYPE_ARRAY = 3;
24+
const TYPE_OBJECT = 4;
25+
const TYPE_RESOURCE = 5;
2626

27-
const STRING_BINARY = 'bin';
28-
const STRING_UTF8 = 'utf8';
27+
const STRING_BINARY = 1;
28+
const STRING_UTF8 = 2;
2929

30-
const ARRAY_ASSOC = 'assoc';
31-
const ARRAY_INDEXED = 'indexed';
30+
const ARRAY_ASSOC = 1;
31+
const ARRAY_INDEXED = 2;
3232

3333
public $type = self::TYPE_REF;
3434
public $class = '';
@@ -38,4 +38,20 @@ class Stub
3838
public $refCount = 0;
3939
public $position = 0;
4040
public $attr = array();
41+
42+
/**
43+
* @internal
44+
*/
45+
public function serialize()
46+
{
47+
return \serialize(array($this->class, $this->position, $this->cut, $this->type, $this->value, $this->handle, $this->refCount, $this->attr));
48+
}
49+
50+
/**
51+
* @internal
52+
*/
53+
public function unserialize($serialized)
54+
{
55+
list($this->class, $this->position, $this->cut, $this->type, $this->value, $this->handle, $this->refCount, $this->attr) = \unserialize($serialized);
56+
}
4157
}

0 commit comments

Comments
 (0)