Skip to content

repsel: array-callback parameters carry no element-shape fact — 10.1% vs the equivalent for-loop (#7151 item 2) #8103

Description

@proggeramlug

Successor to #7151 item 2, split out with a measurement so it is one live
quantified item instead of one of five unranked ones. #7151 is being closed;
this is the only one of its five forms with a material number.

The gap

A.map(cb) / A.forEach(cb) / A.reduce(cb, …) callback parameters carry
no element-shape fact, from either mechanism:

So every r.field in a callback body re-enters the guard diamond that both
mechanisms delete for the equivalent for loop.

Measured on main @ 514ff7c9a

macOS arm64, --release compiler and --release
libperry_{runtime,stdlib}.a, PERRY_RUNTIME_DIR pinned, /usr/bin/time -l
instructions retired, medians of 3 interleaved reps. Both programs build the
same 20,000-element C[], read two fields off every element 200 times (4M
element reads), and print the identical t: 80000000000.

// baseline — the covered form
for (let i = 0; i < arr.length; i++) { const r = arr[i]; s = s + r.a + r.b; }

// probe — the same work through a callback
arr.forEach((r: C) => { s = s + r.a + r.b; });
Ptr<Shape> consumed instructions retired vs baseline
for + const r = A[i] 2 15,800,121,583
A.forEach(cb) 0 17,391,614,644 +10.1%

--opt-report on the forEach probe: Ptr<Shape> 0 selected / 1 denied,
*** Ptr<Shape> promoted 0 of 1 candidates in this build. ***

For scale, the other four forms in #7151 measure −0.3% (direct A[i].field,
already served by the versioned clone), +1.5% (let r = A[i]), ≲1% (sort
then read), and ~0 for the element-rule half of non-empty literals — full table
and method in #7151 (comment).

Why it is worth its own issue

#7170's profile of benchmarks/app-patterns/kernels/batch.ts put the reducer's
acc[idx] field traffic (js_object_set_field_by_name 3.57% +
js_put_value_set_dyn_ic_miss 3.14% inclusive) in exactly this position, and
#7151 item 2 already named it: "it is where batch.ts's hot field traffic
actually lives"
.

Two shapes, and they are not the same problem

  1. forEach/map element parameter — one array, one class, the parameter
    is the element. This is the narrow case and the 10.1% above.
  2. reduce accumulator parameteracc[idx] inside the reducer, i.e. a
    second array reached through a parameter. Needs the array-level fact to
    cross the call boundary, not just the element fact.

(1) alone would be worth scheduling if it can be done as clone-and-route on the
callback with an all-call-sites-agree obligation, which is #6925's precedent.

Not measured

x86-64; --profile dist; map/reduce separately from forEach; any real
dependency-JS population (#7152 measured the outbound call argument escape at
5 of 746 there, which is a different direction from this inbound one); and I did
not attempt an implementation, so there is no cost estimate for the
clone-and-route side.

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