You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf: one Object.setPrototypeOf on an array puts every element store on the full write barrier — 33x whole-program on a fixture #10593
A single Object.setPrototypeOf(someArray, {...}) anywhere in a program made the whole program 33× slower on the fixture where I hit it — 1.51 G → 50.0 G retired instructions — because it permanently moves every array element store onto the full write barrier.
Found incidentally while measuring something else, so treat the framing carefully: I am not claiming this is a bug.invalidate_array_index_fast_path is presumably doing something the collector needs. But the cost is a cliff rather than a slope, it is triggered by one line of ordinary JavaScript, and nothing warns. That seems worth a deliberate decision rather than an accident.
Measurement
Four fixtures differing by three lines, all compiled against the identical runtime archive (this matters — an earlier same-shaped A/B of mine was invalid precisely because the arms were built at different times):
program
retired instructions
baseline
1.51 G
+ Object.setPrototypeOf(anArray, { … })
50.0 G
Attribution of the +48.5 G:
symbol
delta
mark_dirty_old_page_uncached
+29.6 G
hashbrown insert (the remembered set)
+13.6 G
remember_old_to_young_slot
+3.6 G
That is the old→young write barrier taken on every element store, where the index fast path used to serve them.
For contrast, on the same fixture family these do not trigger it: class MyErr extends Error with a live instance, and live subclass instances of Array, Map, Set and Error. Only the explicit setPrototypeOf on a non-GC_TYPE_OBJECT owner does.
Why it may matter beyond a microbenchmark
Object.setPrototypeOf on an array is legal, occasionally used by real libraries (subclass emulation, branding, Array.prototype extensions in older code), and there is no diagnostic. A program that does it once at startup pays for it on every subsequent element store, for the life of the process.
Two questions someone who owns this path should answer, and I have not:
Is the invalidation required to be global and permanent, or could it be scoped to the array whose prototype changed, or to the shape/class that acquired a non-default prototype? The measurement says every element store pays, not only stores to that array.
Is the full barrier the necessary consequence, or only the currently-implemented one?
Context
Measured on perrymaster, base c8cf45056, with the same instrumented-build discipline as #10362's census: node-identical output verified on every arm, and counts reconciled in-process against PERRY_GC_TRACE.
Related but distinct: #10493 is about correctness of a custom prototype on a non-object owner across a GC move (fixed in #10552). This issue is about the performance consequence of setting one at all.
Activity
test: drop push/pop cases that fail identically without this change (#…
perf(array): scope a retargeted array's prototype to that array (#10593)
test: drop push/pop cases that fail identically without this change (#…
perf(array): scope a retargeted array's prototype to that array (#10593)
test: drop push/pop cases that fail identically without this change (#…