Skip to content

perf(compiler): mark struct values with a string property - #550

Merged
paralin merged 1 commit into
s4wave:devfrom
cogentcore:perf/struct-value-marker
Oct 25, 2026
Merged

paralin merged 1 commit into
s4wave:devfrom
cogentcore:perf/struct-value-marker

Conversation

@rcoreilly

Copy link
Copy Markdown
Contributor

Making a struct got about ten times slower under JavaScriptCore (bun, and Safari) with ef7791d, which stores struct fields as own properties of the instance. The cause is the struct value marker that every generated constructor sets, a symbol-keyed property: on a new object with own field properties, a symbol-keyed store takes a slow path in JavaScriptCore. In base/goscript/gsbench of Cogent Core, LadderStructReturn went from 3,246 to 40,114 ns/op under bun between ec20c61 and ef7791d. V8 (node, Chrome) is not affected. Writing the marker under a literal string name restores the speed; a computed key, even a string one, does not.

The marker is now the string __goStructValue, which the emitter writes by name in the constructor of each struct class. A string key is enumerable, so builtin gains ownKeys, which is Object.keys without the marker, and it replaces Object.keys where the keys of a struct are taken as its fields: reflect.DeepEqual, struct equality, the hashing of struct map keys, print, fmt's struct and object formatting, encoding/json's fallback field list and plain object encoding, hash/maphash, unique, structural type matching and the forwarding of struct views. isEmptyObject skips it.

With this, under bun: LadderStructReturn 40,000 -> 3,166 ns/op, StructCopy 17 -> about 1, StructValueMethod 8.5 -> 3.5, LadderStructValue 11,000 -> 5,200, LadderPointerMethod 13,556 -> 5,330, LadderStructPointer 11,016 -> 5,569, BytesBufferPointer 352 -> 180; the whole gsbench suite is about 10% faster by geometric mean.

The compliance suite, typecheck and vitest pass; the regenerated fixture outputs change only in how the constructors set the marker.

The constructor of every struct class sets the struct value marker, which was a symbol-keyed property. Since struct fields became own properties of the instance, setting a symbol key on the new object made making a struct about ten times slower under JavaScriptCore (bun, Safari): a struct return in a loop went from 3.2 to 40 microseconds. V8 is not affected.

The marker is now the string property __goStructValue, which the constructors set by name. As a string key it is enumerable, so ownKeys, Object.keys without it, replaces Object.keys where the keys of a struct are its fields: DeepEqual, struct equality and map key hashing, printing, fmt, encoding/json, maphash, unique and structural type matching.

Signed-off-by: Randall C. O'Reilly <oreilly@ucdavis.edu>
@paralin
paralin changed the base branch from master to dev October 25, 2026 18:39
@paralin
paralin merged commit ee741d4 into s4wave:dev Oct 25, 2026
7 checks passed
@paralin

paralin commented Oct 25, 2026

Copy link
Copy Markdown
Member

Landed on master as eefc23f, rebased onto the batch #532 to #550, with regenerated fixtures in fca8071. Removed RuntimeHelperStructValueMarker, which the emitter no longer uses, folded the double map in fmt's object formatting into one, and made compareMapKeys (from #548) compare struct keys by ownKeys, so the string marker is not compared as a field.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants