Skip to content

Store array length inline to avoid a per-array PropertyDescriptor#2540

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf/array-length-inline
Jun 17, 2026
Merged

Store array length inline to avoid a per-array PropertyDescriptor#2540
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf/array-length-inline

Conversation

@lahma

@lahma lahma commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Problem

Every array allocates one PropertyDescriptor for its length own property (in the ArrayInstance constructors). This is the dominant descriptor cost of array-heavy code: e.g. of the ~27k PropertyDescriptors allocated by the dromaeo-object-array benchmark, essentially all come from array creation ([], new Array(n), species-create), not element storage — the dense element path is already descriptor-free.

Approach

length is always a data property (it is non-configurable, so it can never become an accessor) with only two states: writable (default) or non-writable. So the value is stored inline (_lengthValue) and a descriptor (_lengthDescriptor) is materialized and cached only when a caller needs the object — [[GetOwnProperty]], [[DefineOwnProperty]] (including ArraySetLength and making length non-writable), or reflection. Once materialized the descriptor is authoritative.

Inline state always carries the default attributes, because the only ways to change length's attributes go through DefineLength, which materializes the descriptor first. The hot paths — construction, push, length=, GetLength — read/write the inline value with no descriptor allocation. Array.prototype keeps an explicit descriptor (one-time per realm, preserves its exact attributes).

Benchmarks

ArrayAllocBenchmark (added here), default job, net10.0, 200k arrays/op:

Method Allocated (before → after) Time
LiteralEmpty ([]; a.length=4) 37.77 → 33.20 MB (−12.1%) 38.4 → 39.7 ms
NewArray (new Array(8)) 37.81 → 33.23 MB (−12.1%) 35.9 → 33.7 ms
LiteralSmall ([i,i+1,i+2]) 58.48 → 53.91 MB (−7.8%) 42.4 → 41.0 ms
PushLoop ([]; push×2) 54.17 → 49.59 MB (−8.5%) 72.3 → 70.1 ms

Each array creation drops one 32 B PropertyDescriptor; time is neutral.

Testing

test262 0 failures (99,260 passed) — including the full array suite; Jint.Tests 0 (net10.0 3131 / net472 3069); Jint.Tests.PublicInterface 0.

🤖 Generated with Claude Code

Every array allocated one PropertyDescriptor for its `length` own property
(in the ArrayInstance constructors), which is the dominant descriptor cost of
array-heavy code — e.g. ~27k descriptors in the dromaeo-object-array benchmark
come from array creation, not element storage (the dense element path is
already descriptor-free).

length is always a data property (it is non-configurable, so it can never
become an accessor) with only two states: writable (default) or non-writable.
Store the value inline (_lengthValue) and materialize + cache a descriptor
(_lengthDescriptor) only when a caller needs the object: [[GetOwnProperty]],
[[DefineOwnProperty]] (including ArraySetLength and making length
non-writable), or reflection. Once materialized the descriptor is
authoritative. Inline state is always the default attributes, because the only
ways to change length's attributes go through DefineLength, which materializes
first. The hot paths (construction, push, length=, GetLength) use the inline
value with no descriptor allocation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lahma
lahma merged commit 5ab000b into sebastienros:main Jun 17, 2026
4 checks passed
@lahma
lahma deleted the perf/array-length-inline branch July 13, 2026 06:52
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.

1 participant