Skip to content

perf: inline shape-stable PropertyGet/Set for JSON-parsed records + object literals #51

Description

@proggeramlug

Context

honest_bench JSON pipeline (100 records, 21 KB input). Post-#47/#48 median: Perry 222 ms vs Zig 36 ms (6.2×). Profile of a 2000-iteration loop variant shows 96% of samples sit on the return address of a bl target that reads offset 0x20 of a heap header and does a thread-local lookup — i.e. a generic property lookup firing once per r.id/r.name/r.email/etc. access inside the hot transform loop.

The pattern

const records = JSON.parse(text) as any[];     // homogeneous-shape records
for (let i = 0; i < records.length; i++) {
  const r = records[i];
  if (r.active !== true) continue;              // ← hot
  out.push({
    id: r.id, name: r.name, email: r.email,    // ← each .foo = lookup
    age: r.age, country: r.country, tags: r.tags,
    score: r.score, active: r.active, addr: r.addr,
    display_name: r.name.toUpperCase(),
    age_group: age < 30 ? 'young' : age < 50 ? 'mid' : 'senior',
  });
}

Every .field on r goes through the generic lookup. At 100 records × 11 fields × 2000 outer iters = 2.2M lookups, the 30-instruction helper path dominates.

Proposal — polymorphic inline cache (PIC) on shape-stable receivers

When a hot PropertyGet(receiver, name) is statically typed (r: any[]'s element shape stable across records) or observed monomorphic, emit:

; at each hot access site:
ldr   w_shape, [x_obj, #shape_offset]          ; 1 insn: shape id
cmp   w_shape, #CACHED_SHAPE
b.ne  .slow_path
ldr   x_val,   [x_obj, #CACHED_FIELD_OFFSET]   ; 1 insn: direct load

— replacing the current 30+-instruction helper call.

Scope ladder

  1. Monomorphic hot site (what this issue mostly covers): one shape seen, one fast path + one slow fallback. This is the 80% win.
  2. Bimorphic / trimorphic: inline up to 3 shape checks before falling back. Standard V8/JSC pattern.
  3. Object-literal shape inference: {id: r.id, name: r.name, ...} constructed from stable-shape r produces a statically-predictable output shape; allocate once with shape pre-cached.

Impact projection

JSON pipeline at 100 records: 222 ms → ~70–100 ms band (2-3× remaining gap vs Zig's 36 ms is then the fixed cost of parse + stringify, not the transform loop).

Secondary interactions

  • Unblocks JSON.stringify of homogeneous-shape arrays — a shape-aware stringifier can iterate the known offset table once instead of hashing each key per record.
  • Unblocks the same-shape output object allocation in the .push({…}) — if the shape is known at compile time, the arena allocator can bump-allocate N slots and fill them directly from the source record's offsets.

Repro

benchmarks/honest_bench/workloads/1_json_pipeline/perry/json_pipeline.ts on assets/input_small.json.

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