Skip to content

Commit 5116a1f

Browse files
Add direct-write fast path for bare-name keys in MANGLED_VARS mode
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.
1 parent 9072a51 commit 5116a1f

1 file changed

Lines changed: 19 additions & 0 deletions

File tree

deepclone.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3101,6 +3101,25 @@ PHP_FUNCTION(deepclone_hydrate)
31013101
/* Bare name — find declaring class */
31023102
real_name = prop_key;
31033103
zend_property_info *pi = zend_hash_find_ptr(&obj_ce->properties_info, real_name);
3104+
3105+
/* Fast path: the resolved property has a real backing slot (typed or
3106+
* non-hooked). Write directly via dc_write_backed_property and skip the
3107+
* scoped_props accumulation + second-pass write. This is the hot case
3108+
* for Doctrine-style `fetchAll()` flows where the row is a flat dict
3109+
* of declared field names. */
3110+
if (pi && !(pi->flags & ZEND_ACC_STATIC) && dc_is_backed_declared_property(pi)) {
3111+
zval *v = prop_val;
3112+
if (!(flags & DEEPCLONE_HYDRATE_PRESERVE_REFS)) {
3113+
ZVAL_DEREF(v);
3114+
}
3115+
if (UNEXPECTED(!dc_write_backed_property(Z_OBJ(obj_zval), pi, real_name, v, flags))) {
3116+
if (scoped_owned) zval_ptr_dtor(&local_scoped);
3117+
zval_ptr_dtor(&obj_zval);
3118+
RETURN_THROWS();
3119+
}
3120+
continue;
3121+
}
3122+
31043123
if (pi && !(pi->flags & ZEND_ACC_STATIC)) {
31053124
scope_str = pi->ce->name;
31063125
/* For private-set properties, use the declaring class as write scope */

0 commit comments

Comments
 (0)