Repository navigation
perf: this.a inside an object-literal method costs ~1,085 instructions per call (read path misses every cache) #11420
Description
Activity
Evidence from the package-benchmark attribution work (Phase 3): the harness's bare-loop control does not measure this issue's shape, and on current
mainit has no ~1,000-instruction floor at all.Setup:
origin/main36420d2 (perry 0.5.1654), release compiler, auto-optimize withPERRY_WORKSPACE_ROOT(per-program runtime rebuild), x86-64 (Zen 4),perf stat -e instructions:u, two-N per-iteration method (n = 2M and 8M; startup cancels). Node 26.5.1. Every output was checked equal to Node's.1. The current control (
benchmarks/packages/control/bare_loop.tsonwip/pkg-bench-phase1since 21c9f54, loop bounds hoisted) costs 25 instr/iter (Node: 56). No call survives: LLVM inlinesopandmulFnv. The 25 are 3 ToInt32 range guards, 2cvttsd2si, 3 int→double conversions, the arithmetic, and the loop's induction variable kept in both a double and an i32. There is no object-literal method and nothisin it.2. The historical ~1,040–1,250 was a plain by-name data-property read (
it.nin the loop condition), not a call.control variant Perry instr/iter Node original (first harness commit): Math.imul+i < it.n+header(…, it)1,178 ~0 same, Math.imulreplaced bymulFnv936 55 i < it.n,itfrom importediters(), noheader(…, it)69 55 i < it.n,ita same-module literal passed to a same-module function110 8 current: bounds hoisted 25 56 So about 870 instructions per iteration come from
it.nwhenitis returned by an imported function and passed to an imported function (header). Chain (DWARF call graph):js_class_field_get_ic→js_typed_feedback_class_field_get_guard→class_field_fast_contract→shape_descriptor_by_id(the guard fails), thenjs_object_get_field_by_name_f64→inherited_read_cache_lookup→get_field_by_name_past_inherited_cache→try_data_get_bytes→keys_find_slot_by_bytes_resolved/is_anon_shape_class_id. That is the receiver-provenance read family (#10769 / #10761), not an object-literal method.Math.imuladded about 240 per call on top.3. This issue's own shape, measured the same way:
const o = { k: 3, m(x: number): number { return (x * this.k + 1) % 1000003; } }; for (let i = 0; i < N; i++) acc = o.m(acc + i);
call shape (same body) Perry instr/call Node object-literal method + this.k(this issue)4,267 24 class method + this.k51 41 direct non-inlined function call 86 23 closure held in a const298 24 f.call(o, x)3,746 113 function P(){}; P.prototype.m = …; o.m(x)9,596 34 private this.#m(x)readingthis.#k1,023 37 class getter o.k483 32 In this microbenchmark the object-literal method costs about 4.3k per call. 76% of that is the dispatcher under
js_typed_feedback_native_call_method_by_id(js_native_call_method,js_native_call_value,dispatch_handle,shape_descriptor_by_id). 16% is thethis.kread throughjs_object_get_field_by_name_f64. So the dispatch costs more than thethis.aread in this shape. Your ~1,085 may be the read alone; the call adds the rest here.Conclusion for the harness: the control is not representative of #11420, and after 21c9f54 it no longer carries any floor: 25 instr/iter, below Node. The Phase-1 interim per-iteration numbers at 2febf42 included about 0.9–1.2k instr/iter of harness overhead per iteration (
it.n+Math.imul). That is negligible for the heavy workloads (≥50k/iter) but material for the light ones. The attribution report re-measures everything on the hoisted harness.- added a commit that references this issue
on Sep 27, 2026
Reading
this.ainside an object-literal method costs about 1,085 instructions per call on the current perf branches. The same read costs about 180 in an ES5 prototype method, and about 200 when the receiver is passed to a helper as a parameter.The cost is in the read path, not in binding
this: every lookup cache misses on this route. #11326 bound the literal method'sthisonce for the object-literal fast path, but this call shape still reaches a route whose property read never primes an inline cache.Found by the
this-as-parameter census, measured on the perf-method-calls base, instructions per call from the 1M/5M slope. Wanted: the read should take the ordinary property IC, at the same cost as a class method'sthis.a.