Skip to content

perf: OBJ_FLAG_HAS_DESCRIPTORS is per-OBJECT, so one Object.defineProperty on one key takes every other key of that object off BOTH property ICs — 15.8x (126 -> 1991 instr/iteration, node 17.4 -> 20.8) #10871

Description

@proggeramlug

OBJ_FLAG_HAS_DESCRIPTORS is a per-object header bit, and both property inline caches refuse any receiver that carries it — the read PIC (property_get/generic_dispatch.rs, bit 11 of _reserved) and the write PIC (proxy_reflect.rs, bit 11 of WRITE_PIC_BLOCKING_FLAGS = 0x1987). So one Object.defineProperty on ONE key takes every OTHER key of that object off both fast paths, permanently.

Measured (v0.5.1628, perf stat -e instructions:u, min of 3, per-iteration fitted between N=500k and N=5M, perry flat across the range, output byte-identical to node)

Two fixtures differing by one line. Loop body is O.a = k; h += O.a; in both — a is an ordinary writable data property in both, and is never the key the descriptor is installed on.

const O = {a:1, b:2, c:3, d:4};
// q1 only:
Object.defineProperty(O, 'z', {value:7, writable:false, enumerable:false, configurable:false});
function run(n){let h=0;for(let k=0;k<n;k++){O.a=k;h+=O.a;}return h;}
perry node bun
no descriptor anywhere on the object 126.00 17.37 10.82
one non-writable z installed, never touched again 1991.00 20.76 9.84
cost of the unrelated descriptor +1865 (15.8×) +3.4 ~0

Where the 1865 goes (callgrind --separate-callers=1, --debug-symbols build, per iteration)

Both halves of the loop fall out of cache, not just the store:

  • read: get_field_ic_miss_impl 253, native_get::try_data_get_bytes 215, keys_find_slot_by_bytes_resolved 81, inherited_read_cache_lookup 80, shape_descriptor_by_id 70, is_anon_shape_class_id 54, array_subclass_named_prefix_token_for_slot 51, memcmp 50
  • store: js_put_value_set_ic_miss 128, try_existing_own_data_overwrite 146, own_descriptors_skip_key 75, shape_descriptor_by_id 70, is_class_object_ptr 67, object_keys_array 53, read_plan_lookup 49, runtime_store_jsvalue_slot + layout_note_slot 91, js_put_value_set 45

own_descriptors_skip_key at 75 per store is the tell: the runtime does ask per key whether the key being stored has a descriptor, and answers "no" every time — 200 000 times — while the emitted guard has already refused on the per-object bit before reaching it.

Why this is worth fixing rather than documenting

This is the pattern real code has. A library that calls Object.defineProperty(exports, '__esModule', {value: true}), or installs one non-enumerable name/length/toJSON, or freezes a single constant on a config object, makes every ordinary property of that object 15.8× dearer for the life of the process. The object does not have to be exotic in any way a reader would notice, and there is no diagnostic that points at it.

Design OBJECT_MODEL_SINGLE_PATH_DESIGN_2026-09-20.md §3 already names the fix: per-key attributes belong in the shape, not in an address-keyed side table with a per-object "somewhere in here there is a descriptor" bit. With per-key attributes in the shape, a shape match proves the stored key is a plain writable data property, and the sibling descriptor costs the object nothing. §8 step 3 has it as work; this issue is the measurement that says what it is worth.

Interim, much cheaper option if step 3 is far off: make the bit mean "this object has a descriptor on a key the site has not primed" by consulting the shape's descriptor key set at prime time and letting the emitted guard trust the ShapeId — i.e. move the per-key question to the miss, which runs once, from the hit, which runs 200 000 times. That needs #10824's guarantee that every descriptor install transitions the shape, which landed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions