Direct-write fast path for bare-name keys in MANGLED_VARS mode#13
Merged
nicolas-grekas merged 2 commits intomainfrom Apr 15, 2026
Merged
Direct-write fast path for bare-name keys in MANGLED_VARS mode#13nicolas-grekas merged 2 commits intomainfrom
nicolas-grekas merged 2 commits intomainfrom
Conversation
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.
2f08869 to
5116a1f
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When callers pass a flat
$varsdictionary of bare property names (no"\0ClassName\0prop"mangling), the MANGLED_VARS parser previously resolved each key to(scope, name)and stashed it in an intermediatescoped_propsHashTable, 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_infofor each bare key and — when it points at a real backed declared property — callsdc_write_backed_property()immediately, skippingscoped_propsaccumulation 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:deepclone_hydrate($class, $flatRow, MANGLED_VARS)setValueloop (reference, unchanged)deepclone_hydratescoped (pre-built scoped shape, ideal)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
ObjectHydratorhas its data is a flat[field => value]dict from PDO. Before this commit, to take advantage of the ext they had to either:[scope => [field => value]]shape per row (scope-grouping tax eats the speedup), or"\0Class\0field"inClassMetadataand remap each row through them (more hashing, same intermediate-hashtable penalty).With this fast path, they can pass
$rowas-is withDEEPCLONE_HYDRATE_MANGLED_VARSand get full ext-speed hydration.Test plan
.phpttests green locally (PHP 8.4, NTS)