Skip to content

Evaluate sum-of-products assignments on raw doubles, boxing once#2611

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf-sum-of-products
Jul 9, 2026
Merged

Evaluate sum-of-products assignments on raw doubles, boxing once#2611
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf-sum-of-products

Conversation

@lahma

@lahma lahma commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

What

M[i][j] = M1[i][0]*M2[0][j] + M1[i][1]*M2[1][j] + … — the dromaeo-3d-cube MMulti/VMulti kernel
shape — boxed a transient JsNumber per binary node (~7 per matrix cell; 51% of the cube's
allocation in this campaign's Phase-0 census). Assignment right-hand sides that form a left-deep
± chain of two or more products over pure-readable numeric leaves
now compute on raw doubles and
box a single result.

Leaves are numeric constants, slot/global identifiers, and one- or two-level dense computed reads
with identifier bases (v[0], m[i][j]).

Safety and semantics

  • Every runtime leaf read is pure (slot/validated-global reads, TryGetValueFast dense probes),
    so a decline — leaf not dense, not a Number, TDZ — falls back to generic tree evaluation with no
    observable double effects. A Proxy-trap count test pins exactly-once evaluation on decline.
  • Double arithmetic is the spec arithmetic (Number::multiply/add), so integer-valued leaves
    produce identical results; 0 * negative correctly yields −0 (Object.is-pinned).
  • The accumulator seeds from the first term rather than +0 — a phantom leading zero would turn a
    (−0) + (−0) sum into +0 (a pin caught this during development) — and only left-deep chains
    arm
    , so the linear fold reproduces the tree's association, and therefore its floating-point
    rounding, exactly.
  • Armed only for all-product chains (≥2 terms), a shape integer-heavy workloads don't produce —
    their dedicated per-op integer paths never see this probe (the constraint from the earlier
    unboxed-islands attempt).

Results

A/B vs main (default job; base side via source checkout since the change was committed):

measurement before after
dromaeo-3d-cube driver (±0.7% class) 30.83 ms/iter 26.55 ms/iter −13.9%
cube allocation census 2.03 MB/iter 1.31 MB/iter −35.5% (JsNumber share 51% → 18%)
dromaeo-object-array driver (integer canary) 40.42 ms/iter 36.04 ms/iter favorable/noise
ElementAccess Read / RMW / Append 95.4 / 195.6 / 39.2 ms 93.9 / 187.4 / 38.4 ms −1.6 / −4.2 / −2.0%
ElementAccess Write / ChainedRead (untouched) 96.7 / 134.7 ms 101.4 / 138.9 ms +3–5%, inside this family's documented swing; allocations byte-identical on every row

Gating

All-TFM build ✅ · Jint.Tests ×2 ✅ · PublicInterface ×2 ✅ · Test262 99,426/0 ✅

🤖 Generated with Claude Code

M[i][j] = M1[i][0]*M2[0][j] + M1[i][1]*M2[1][j] + ... - the
dromaeo-3d-cube MMulti/VMulti kernel shape - boxed a transient JsNumber
per binary node (~7 per matrix cell, 51% of the cube's allocation in
the Phase-0 census). Assignment right-hand sides that form a left-deep
+/- chain of two or more products over pure-readable numeric leaves now
compute on raw doubles and box a single result.

Safety and semantics:
- leaves are numeric constants, slot/global identifiers and dense
  computed reads with identifier bases; every runtime read is pure, so
  a decline (leaf not dense, not a Number, TDZ) falls back to generic
  tree evaluation with no observable double effects - a Proxy-trap
  count test pins exactly-once evaluation on decline
- double arithmetic IS the spec arithmetic (Number::multiply/add), so
  integer-valued leaves produce identical results; 0 * negative
  correctly yields -0 (Object.is-pinned)
- the accumulator seeds from the first term rather than +0 - a phantom
  leading zero would turn a (-0) + (-0) sum into +0 - and only
  left-deep chains arm, so the linear fold reproduces the tree's
  association and floating-point rounding exactly
- armed only for all-product chains (>= 2 terms), a shape integer-heavy
  workloads don't produce - their dedicated per-op integer paths never
  see this probe

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lahma
lahma merged commit 5b0c151 into sebastienros:main Jul 9, 2026
7 of 8 checks passed
@lahma
lahma deleted the perf-sum-of-products branch July 9, 2026 14:07
lahma added a commit that referenced this pull request Jul 9, 2026
Full re-run after the 2026-07 interpreter-parity campaign
(#2600-#2606, #2608-#2611). Highlights vs the 2026-07-07 refresh:
dromaeo-core-eval -40% (1.52 -> 0.92 ms, taking first place from
NiL.JS), dromaeo-3d-cube -19% time / -35% allocation (7.38 -> 5.99 ms,
moving ahead of NiL.JS - only the IL-compiling engine remains faster),
stopwatch -7% (tied with the best interpreter, ahead of Jurassic),
stopwatch-modern -13% (tied with Jurassic), object-array -10%,
object-regexp -13%, object-string -5%. Competitor versions unchanged;
their rows served as thermal canaries (all within a few percent of the
previous refresh).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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