Commit 94e1c84
committed
feature #574 [DeepClone] Add DEEPCLONE_HYDRATE_PRESERVE_REFS flag + hot-path perf (nicolas-grekas)
This PR was squashed before being merged into the 1.x branch.
Discussion
----------
[DeepClone] Add DEEPCLONE_HYDRATE_PRESERVE_REFS flag + hot-path perf
Mirrors [symfony/php-ext-deepclone#12](symfony/php-ext-deepclone#12) on the polyfill side.
## 1. PRESERVE_REFS flag (BC break)
By default, `deepclone_hydrate()` now drops PHP `&` references from `$vars` on write; pass the new `DEEPCLONE_HYDRATE_PRESERVE_REFS` flag to keep ref links. Callers that intentionally share a value slot between properties need to add the flag.
`symfony/var-exporter`'s `Hydrator` / `Instantiator` pass the flag by default to preserve the pre-existing contract.
## 2. unserialize()-permissive property-name handling in scoped mode
In scoped mode, the pre-v0.4.0 hot-path validation rejected integer keys, NUL-in-middle names, and mangled-shape keys with a `ValueError`. That was stricter than what `unserialize()` does on the same shapes in an `O:…` payload. The polyfill now matches `unserialize()`:
- **Integer keys** — coerce to string on dynamic property access (PHP engine rule).
- **NUL-in-middle names** — stored as raw dynamic properties.
- **NUL-prefix names** — surface the engine's native `Error: Cannot access property starting with "\0"`.
`DEEPCLONE_HYDRATE_MANGLED_VARS` mode still parses and validates mangled keys (that's the entire point of the flag).
## Perf
The ref-preservation path used to run unconditionally and required a per-call probe (`ReflectionReference::fromArrayElement` over every key) plus a by-ref double-write of every property. The per-prop `str_contains(\$name, \"\\0\")` / `is_string(\$name)` checks were another ~18 ns per property on top. With both relaxed, the polyfill now **matches or beats raw Reflection** for flat-class hydration.
### Individual wins
- **Ref-probe gated on the flag** — default path skips it entirely, lean single-write inner loop.
- **Property-name validation matches `unserialize()`** — dropped the per-prop `is_string + str_contains NUL` check in favor of the engine's native semantics.
- **`use ($notByRef)` closure capture** replacing `(array) $this` — avoids the per-call stdClass→array cast.
- **1-scope inline fast path** in `deepclone_hydrate` — skips the outer `foreach ($scoped_vars as ...)` + cross-scope validation for the most common shape.
- **Three closure variants each split by `$hasRefs`** — lean no-refs loop sits next to the ref-preserving double-write loop, branch-predicted once per call.
### Results
PHP 8.4 / opcache, 100k iters, realistic DTO hydration:
| case | before | after | vs Reflection |
|----------------------------------------|-------:|------:|--------------:|
| 6-prop mixed (pub + priv + prot) | 1,483 | 812 | 1.36× |
| 8 typed props | 1,766 | 1,077 | 1.32× |
| stdClass, 5 props | 1,182 | 609 | — |
| 3-level inheritance | 1,932 | 1,128 | 3.80× |
| readonly via ctor promotion | 1,519 | 1,144 | 2.35× |
Scenarios 3 (3-level inheritance) and 4 (readonly) still lag Reflection — residual outer multi-scope iteration and per-readonly-prop `ReflectionProperty` invocation inside the hydrator. Flat-class hydration (scenarios 1, 2, 5) is at or below Reflection.
## Test plan
- [x] 374/374 DeepClone tests green locally
- [ ] CI green across the PHP matrix
Commits
-------
7e0c66d [DeepClone] Match unserialize() permissiveness on scoped-mode prop names
a1c9ed9 [DeepClone] Add DEEPCLONE_HYDRATE_PRESERVE_REFS flag + hot-path perf3 files changed
Lines changed: 178 additions & 80 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
372 | 372 | | |
373 | 373 | | |
374 | 374 | | |
375 | | - | |
| 375 | + | |
376 | 376 | | |
377 | 377 | | |
378 | 378 | | |
379 | 379 | | |
380 | 380 | | |
381 | 381 | | |
382 | | - | |
383 | | - | |
384 | | - | |
385 | | - | |
386 | | - | |
387 | | - | |
388 | | - | |
389 | | - | |
390 | 382 | | |
391 | 383 | | |
392 | 384 | | |
| |||
406 | 398 | | |
407 | 399 | | |
408 | 400 | | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
409 | 429 | | |
410 | 430 | | |
411 | 431 | | |
| |||
427 | 447 | | |
428 | 448 | | |
429 | 449 | | |
430 | | - | |
| 450 | + | |
431 | 451 | | |
432 | 452 | | |
433 | 453 | | |
| |||
468 | 488 | | |
469 | 489 | | |
470 | 490 | | |
471 | | - | |
472 | | - | |
473 | | - | |
474 | | - | |
475 | | - | |
476 | | - | |
477 | | - | |
478 | | - | |
479 | 491 | | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
480 | 504 | | |
481 | 505 | | |
482 | 506 | | |
| |||
485 | 509 | | |
486 | 510 | | |
487 | 511 | | |
488 | | - | |
| 512 | + | |
489 | 513 | | |
490 | 514 | | |
491 | 515 | | |
| |||
1322 | 1346 | | |
1323 | 1347 | | |
1324 | 1348 | | |
1325 | | - | |
1326 | | - | |
1327 | | - | |
1328 | | - | |
| 1349 | + | |
| 1350 | + | |
| 1351 | + | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
| 1357 | + | |
| 1358 | + | |
1329 | 1359 | | |
1330 | 1360 | | |
1331 | 1361 | | |
| |||
1379 | 1409 | | |
1380 | 1410 | | |
1381 | 1411 | | |
1382 | | - | |
| 1412 | + | |
1383 | 1413 | | |
1384 | 1414 | | |
1385 | 1415 | | |
| |||
1388 | 1418 | | |
1389 | 1419 | | |
1390 | 1420 | | |
1391 | | - | |
| 1421 | + | |
1392 | 1422 | | |
1393 | 1423 | | |
1394 | 1424 | | |
1395 | 1425 | | |
1396 | | - | |
| 1426 | + | |
1397 | 1427 | | |
1398 | | - | |
| 1428 | + | |
1399 | 1429 | | |
1400 | 1430 | | |
1401 | 1431 | | |
1402 | 1432 | | |
1403 | | - | |
| 1433 | + | |
1404 | 1434 | | |
1405 | 1435 | | |
1406 | | - | |
1407 | | - | |
| 1436 | + | |
1408 | 1437 | | |
1409 | 1438 | | |
1410 | 1439 | | |
| |||
1424 | 1453 | | |
1425 | 1454 | | |
1426 | 1455 | | |
1427 | | - | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
1428 | 1460 | | |
1429 | | - | |
1430 | | - | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
| 1471 | + | |
| 1472 | + | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
| 1476 | + | |
| 1477 | + | |
| 1478 | + | |
| 1479 | + | |
| 1480 | + | |
| 1481 | + | |
| 1482 | + | |
| 1483 | + | |
| 1484 | + | |
| 1485 | + | |
| 1486 | + | |
| 1487 | + | |
| 1488 | + | |
| 1489 | + | |
| 1490 | + | |
| 1491 | + | |
| 1492 | + | |
| 1493 | + | |
| 1494 | + | |
| 1495 | + | |
| 1496 | + | |
| 1497 | + | |
| 1498 | + | |
| 1499 | + | |
| 1500 | + | |
| 1501 | + | |
| 1502 | + | |
| 1503 | + | |
| 1504 | + | |
| 1505 | + | |
| 1506 | + | |
| 1507 | + | |
| 1508 | + | |
| 1509 | + | |
| 1510 | + | |
| 1511 | + | |
| 1512 | + | |
| 1513 | + | |
| 1514 | + | |
| 1515 | + | |
| 1516 | + | |
| 1517 | + | |
| 1518 | + | |
| 1519 | + | |
| 1520 | + | |
| 1521 | + | |
| 1522 | + | |
1431 | 1523 | | |
| 1524 | + | |
| 1525 | + | |
| 1526 | + | |
| 1527 | + | |
| 1528 | + | |
| 1529 | + | |
| 1530 | + | |
1432 | 1531 | | |
1433 | 1532 | | |
1434 | 1533 | | |
| |||
1438 | 1537 | | |
1439 | 1538 | | |
1440 | 1539 | | |
1441 | | - | |
1442 | | - | |
1443 | | - | |
1444 | | - | |
1445 | | - | |
1446 | | - | |
| 1540 | + | |
| 1541 | + | |
1447 | 1542 | | |
1448 | 1543 | | |
1449 | 1544 | | |
1450 | 1545 | | |
| 1546 | + | |
| 1547 | + | |
| 1548 | + | |
1451 | 1549 | | |
1452 | 1550 | | |
1453 | | - | |
1454 | 1551 | | |
1455 | 1552 | | |
1456 | 1553 | | |
1457 | 1554 | | |
1458 | 1555 | | |
1459 | 1556 | | |
1460 | | - | |
1461 | | - | |
1462 | | - | |
1463 | | - | |
1464 | | - | |
1465 | | - | |
1466 | | - | |
1467 | | - | |
1468 | | - | |
1469 | | - | |
1470 | | - | |
1471 | | - | |
1472 | | - | |
1473 | | - | |
1474 | | - | |
1475 | | - | |
1476 | | - | |
1477 | | - | |
1478 | | - | |
1479 | | - | |
1480 | | - | |
1481 | | - | |
1482 | 1557 | | |
1483 | | - | |
| 1558 | + | |
1484 | 1559 | | |
1485 | 1560 | | |
1486 | 1561 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
33 | 36 | | |
34 | 37 | | |
35 | 38 | | |
0 commit comments