Skip to content

perf: declared-class construction is 63% slower than an equivalent object literal (28.5x vs 17.4x vs node) #7513

Description

@proggeramlug

Symptom

Decomposed churn on the quiet mini (best-of-3), Perry vs node:

variant Perry node ratio
churn (full) 2.72 s 0.17 s 16.0×
churn_alloc — object literal + push 2.44 s 0.14 s 17.4×
push_clsnew Node(v,w) + push 3.99 s 0.14 s 28.5×
push_num — numbers into array 0.30 s 0.11 s 2.7×
churn_read — element reads only 0.35 s 0.08 s 4.3×

A declared class is 63% slower to construct than an equivalent object literal.

That ordering is backwards and is the reason this is filed as a defect rather
than a missing optimization. new Node(v, w) on a fixed-shape declared class
is the most statically-known construction form in the language: the field set,
their order, the class id and the shape are all known at compile time. The
object literal has to mint an anon shape (__AnonShape_…) and still wins.

Whatever the declared-class path is doing extra, it is doing it in spite of
having strictly more static information — so the likely finding is a fast path
that the literal reaches and the class does not, or per-construction work that
the class path repeats and the literal amortizes.

Where to look first

  • perf(codegen): elide provably-dead per-store bookkeeping on class-field stores #7486's elision may not reach declared classes. It elides dead
    default-undefined field inits when the ctor opens with a run of
    this.f = <param> statements, proven by ctor_prologue_param_assigned_fields
    (lower_call/field_init.rs). It was measured on the synthesized anon-shape
    ctor. A user-declared class Node { constructor(v, w) { this.v = v; this.w = w } }
    should qualify — verify it actually does, and if it does not, why.
  • js_gc_init_typed_shape_layout (7.7% of churn_alloc) — is it emitted
    per construction on the class path but hoisted/absent on the literal path?
  • Typed-feedback guards (9.2%) — does the class path instrument sites the
    literal path does not?

Method

Compile both shapes with --trace llvm and diff the per-construction call
census (that is how #7501's admission analysis was verified). The difference
should be visible as concrete calls, not inferred from timings — and it is
cheap to get, which is why this may be a quick bisect rather than a project.

Provenance

Measured on the pinned quiet host, symbolicated with PERRY_DEBUG_SYMBOLS=1,
1500 leaf samples. Part of the #7469 mutator-cost campaign; sibling findings
are the layout side-table cost (commented on #5094) and write-barrier cost.

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