Skip to content

Direct-write fast path for bare-name keys in MANGLED_VARS mode#13

Merged
nicolas-grekas merged 2 commits intomainfrom
mangled-vars-fast-path
Apr 15, 2026
Merged

Direct-write fast path for bare-name keys in MANGLED_VARS mode#13
nicolas-grekas merged 2 commits intomainfrom
mangled-vars-fast-path

Conversation

@nicolas-grekas
Copy link
Copy Markdown
Member

Summary

When callers pass a flat $vars dictionary of bare property names (no "\0ClassName\0prop" mangling), the MANGLED_VARS parser previously resolved each key to (scope, name) and stashed it in an intermediate scoped_props HashTable, then ran the regular scoped-mode write in a second pass. For the typical ORM flow (fetchAll() → flat row dict of declared field names), that double iteration was pure overhead.

The parser now resolves property_info for each bare key and — when it points at a real backed declared property — calls dc_write_backed_property() immediately, skipping scoped_props accumulation and the second-pass iteration.

NUL-prefix (mangled) keys, unresolved names, and hooked/virtual properties fall through to the original path, so error handling, scope tracking, and per-scope EG(fake_scope) semantics are unchanged.

Benchmark

Customer entity, 11 fields, 3-level inheritance (Customer → Person → Entity), 100-entity batch on PHP 8.4:

shape before after
deepclone_hydrate($class, $flatRow, MANGLED_VARS) 1,275 486
Doctrine's setValue loop (reference, unchanged) 1,131 1,131
deepclone_hydrate scoped (pre-built scoped shape, ideal) 428 428

2.33× speedup over Doctrine's current approach, within ~58 ns of the hand-built scoped-shape path.

Why this matters for Doctrine / ORM users

The natural way a Doctrine-style ObjectHydrator has its data is a flat [field => value] dict from PDO. Before this commit, to take advantage of the ext they had to either:

  1. Build a nested [scope => [field => value]] shape per row (scope-grouping tax eats the speedup), or
  2. Pre-compute mangled keys "\0Class\0field" in ClassMetadata and remap each row through them (more hashing, same intermediate-hashtable penalty).

With this fast path, they can pass $row as-is with DEEPCLONE_HYDRATE_MANGLED_VARS and get full ext-speed hydration.

Test plan

  • 30/30 .phpt tests green locally (PHP 8.4, NTS)
  • CI green across the matrix

When $vars is a flat dictionary of bare property names (no
"\0ClassName\0prop" mangling), the pre-existing MANGLED_VARS parser
resolved each key to (scope, name), stashed it in an intermediate
scoped_props HashTable, then ran the regular scoped-mode write in a
second pass. Pure overhead for the common case.

The parser now resolves the property_info for each bare key and, when
it points at a real backed declared property, writes via
dc_write_backed_property() immediately — skipping the scoped_props
accumulation and the second-pass iteration. NUL-prefix (mangled) keys,
unresolved names, and hooked/virtual properties keep the original path
so error handling, scope tracking, and per-scope EG(fake_scope) stay
unchanged.

Bench, Customer (11 fields, 3-level inheritance), 100-entity batch
on PHP 8.4:

| shape                                               | before | after |
|-----------------------------------------------------|-------:|------:|
| deepclone_hydrate($class, $flatRow, MANGLED_VARS)   |  1,275 |   486 |
| Doctrine setValue loop (reference, unchanged)       |  1,131 | 1,131 |

That's a 2.33x speedup over Doctrine's current approach, down to
within ~58 ns of the hand-built scoped-shape path. The Doctrine pitch
becomes "pass the PDO row dict as-is, done." — no per-row scope
grouping, no mangled-key construction.
@nicolas-grekas nicolas-grekas force-pushed the mangled-vars-fast-path branch from 2f08869 to 5116a1f Compare April 15, 2026 19:11
After the direct-write fast path for bare-name keys, the scoped shape and
the flat-bare-name shape both resolve each key via a single
properties_info hash lookup + direct slot write — one is no longer
"fastest". Frame both as first-class options, highlight the flat-bare-
name shape as the ideal match for PDO-row-style flat dicts, and drop the
stale "PHP & references are preserved" claim now that PRESERVE_REFS is
opt-in.
@nicolas-grekas nicolas-grekas merged commit 71a5d7f into main Apr 15, 2026
20 checks passed
@nicolas-grekas nicolas-grekas deleted the mangled-vars-fast-path branch April 16, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant