Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
508 changes: 234 additions & 274 deletions benchmarks/packages/REPORT.md

Large diffs are not rendered by default.

176 changes: 176 additions & 0 deletions benchmarks/packages/RERUN-2026-09-30.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Package profile: where Perry's instructions go

Perry commit `9e29f59d43`; call chains: `perf record -e instructions:u --call-graph dwarf`; ~12000 samples at n2; every figure is instructions **per iteration** by the two-N method (the n1 profile is subtracted from the n2 profile key by key). Generated by `scripts/package_bench.py profile --callgraph`.

## Ranked root-cause buckets

Share of Perry's **excess over Node** (perry − node instructions/iter). *Equal-weight*: mean over packages (each package counts once, its workloads averaged) — the headline. *Abs-weight*: summed absolute excess (dominated by the most expensive workloads, shown for transparency). Workloads with Perry/Node < 2× are excluded.

Control floor (control perry − node, clamped at 0): **0 instr/iter** — nothing to subtract, so the floor-adjusted ranking is identical.

| # | bucket | equal-weight % of excess | abs-weight % | packages where ≥5% | equal-weight, floor-adjusted |
|---|---|---:|---:|---|---:|
| 1 | property lookup slow path / IC miss (own/by-name) (`prop_lookup`) | 34.2 | 34.2 | 1: mongodb | 34.2 |
| 2 | GC: minor/copying collection (`gc_minor`) | 12.1 | 12.1 | 1: mongodb | 12.1 |
| 3 | GC: major (mark-sweep) collection (`gc_major`) | 9.1 | 9.1 | 1: mongodb | 9.1 |
| 4 | dynamic index get/set, Array element access (`dyn_index`) | 6.3 | 6.3 | 1: mongodb | 6.3 |
| 5 | string ops / UTF-8<->UTF-16 transcoding (`string`) | 5.3 | 5.3 | 1: mongodb | 5.3 |
| 6 | runtime method dispatch (obj.m() via dispatcher) (`method_dispatch`) | 4.9 | 4.9 | 0: | 4.9 |
| 7 | Buffer / typed-array access + buffer-registry probes (`buffer_typedarray`) | 4.2 | 4.2 | 0: | 4.2 |
| 8 | reads resolved on the prototype chain (absent keys, inherited data) (`proto_chain_read`) | 3.9 | 3.9 | 0: | 3.9 |
| 9 | object/array allocation entry points (`alloc`) | 3.3 | 3.3 | 0: | 3.3 |
| 10 | generated code (inline JS) (`generated_code`) | 2.6 | 2.6 | 0: | 2.6 |
| 11 | private class members (`private_fields`) | 2.5 | 2.5 | 0: | 2.5 |
| 12 | GC: other (safepoint bookkeeping, layout notes) (`gc_other`) | 2.2 | 2.2 | 0: | 2.2 |
| 13 | GC write barrier (`write_barrier`) | 1.8 | 1.8 | 0: | 1.8 |
| 14 | property write (add/overwrite slow path) (`prop_write`) | 1.4 | 1.4 | 0: | 1.4 |
| 15 | accessor / property-descriptor handling (`accessor_descriptor`) | 1.1 | 1.1 | 0: | 1.1 |
| 16 | other runtime (`other_runtime`) | 0.9 | 0.9 | 0: | 0.9 |
| 17 | raw allocation (arena/malloc, no stronger owner on the chain) (`raw_alloc`) | 0.9 | 0.9 | 0: | 0.9 |
| 18 | closures / boxed captures / arguments (`closure_box_args`) | 0.9 | 0.9 | 0: | 0.9 |
| 19 | call overhead (safepoints, shadow frames, param guards, dynamic calls) (`call_overhead`) | 0.6 | 0.6 | 0: | 0.6 |
| 20 | Promise / async / timers (`promise_async`) | 0.5 | 0.5 | 0: | 0.5 |
| 21 | Symbol-keyed properties (`symbol`) | 0.4 | 0.4 | 0: | 0.4 |
| 22 | BigInt (`bigint`) | 0.3 | 0.3 | 0: | 0.3 |
| 23 | Map / Set (`map_set`) | 0.3 | 0.3 | 0: | 0.3 |
| 24 | value tests (typeof/equality/truthiness) (`value_typeof`) | 0.2 | 0.2 | 0: | 0.2 |
| 25 | exceptions / try-catch setup (`exceptions`) | 0.1 | 0.1 | 0: | 0.1 |
| 26 | regex (Perex) (`regex`) | 0.0 | 0.0 | 0: | 0.0 |
| 27 | numeric conversion (ToInt32, fmod, trunc, Math) (`numeric_conv`) | 0.0 | 0.0 | 0: | 0.0 |
| 28 | UTF-8 validation / transcoding (no stronger owner on the chain) (`utf8_transcode`) | 0.0 | 0.0 | 0: | 0.0 |
| 29 | memcmp/memcpy (no stronger owner on the chain) (`memops`) | 0.0 | 0.0 | 0: | 0.0 |
| 30 | number <-> string (`number_string`) | 0.0 | 0.0 | 0: | 0.0 |
| 31 | std HashMap/SipHash side tables (no stronger owner on the chain) (`side_table_hash`) | 0.0 | 0.0 | 0: | 0.0 |
| 32 | JSON (`json`) | 0.0 | 0.0 | 0: | 0.0 |
| 33 | unresolved frames (`unknown`) | 0.0 | 0.0 | 0: | 0.0 |

## Package × bucket matrix (% of the package's excess)

| package | `prop_lookup` | `gc_minor` | `gc_major` | `dyn_index` | `string` | `method_dispatch` | `buffer_typedarray` | `proto_chain_read` | `alloc` | `generated_code` | `private_fields` | `gc_other` |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| mongodb | 34 | 12 | 9 | 6 | 5 | 5 | 4 | 4 | 3 | 3 | 3 | 2 |

## Construct view: runtime entry points called from generated code

Everything beneath each entry up to the next JS frame (all buckets combined), equal-weight % of excess. `<inline JS>` is generated code's own instructions; `<no JS frame>` is work with no JS caller (GC cycles, event loop).

| % of excess | runtime entry |
|---:|---|
| 22.7 | `<no JS frame>` |
| 20.8 | `js_native_call_method_by_id` |
| 15.6 | `js_object_get_field_by_property_id_f64` |
| 5.5 | `js_event_emitter_on` |
| 4.1 | `js_object_set_field_by_property_id` |
| 3.2 | `js_object_alloc_class_inline_keys_stamped` |
| 2.8 | `js_object_get_own_field_or_undef` |
| 2.7 | `<inline JS>` |
| 2.2 | `node_stream::push_chunk` |
| 0.9 | `js_class_field_add` |
| 0.8 | `js_async_step_done` |
| 0.8 | `promise::microtasks::run_microtasks` |
| 0.7 | `js_object_assign_one` |
| 0.6 | `object::field_get_set::ic_miss::get_field_ic_miss_impl` |

## Top (bucket, runtime entry) pairs

Which runtime entry point — the function generated code called — carries each bucket, equal-weight % of excess over packages.

| % of excess | bucket | runtime entry called from generated code |
|---:|---|---|
| 13.1 | prop_lookup | `js_object_get_field_by_property_id_f64` |
| 12.1 | gc_minor | `<no JS frame>` |
| 9.1 | prop_lookup | `js_native_call_method_by_id` |
| 9.0 | gc_major | `<no JS frame>` |
| 4.7 | method_dispatch | `js_native_call_method_by_id` |
| 4.3 | dyn_index | `js_event_emitter_on` |
| 3.5 | buffer_typedarray | `js_native_call_method_by_id` |
| 3.2 | alloc | `js_object_alloc_class_inline_keys_stamped` |
| 2.8 | prop_lookup | `js_object_get_own_field_or_undef` |
| 2.7 | generated_code | `<inline JS>` |
| 2.5 | private_fields | `js_native_call_method_by_id` |
| 2.5 | string | `js_object_get_field_by_property_id_f64` |
| 2.5 | prop_lookup | `js_object_set_field_by_property_id` |
| 1.6 | gc_other | `<no JS frame>` |
| 1.2 | write_barrier | `js_event_emitter_on` |
| 1.1 | string | `js_native_call_method_by_id` |
| 1.0 | prop_write | `js_object_set_field_by_property_id` |
| 0.9 | prop_lookup | `js_class_field_add` |
| 0.9 | raw_alloc | `node_stream::push_chunk` |
| 0.8 | proto_chain_read | `js_async_step_done` |
| 0.8 | dyn_index | `promise::microtasks::run_microtasks` |
| 0.7 | other_runtime | `node_stream::push_chunk` |
| 0.7 | prop_lookup | `js_object_assign_one` |
| 0.7 | dyn_index | `node_stream::push_chunk` |
| 0.6 | string | `js_object_set_field_by_property_id` |
| 0.6 | prop_lookup | `object::field_get_set::ic_miss::get_field_ic_miss_impl` |

Regex bucket split (equal-weight % of excess): execute 0.0, compile -0.0

## Workloads

| workload | perry/iter | node/iter | ratio | unwind→main | package code on stack (≥) | top buckets (% of Perry) |
|---|---:|---:|---:|---:|---:|---|
| mongodb/insert_find | 175.70M | 787.6k | 223.1× | 80% | 52% | prop_lookup 34%, gc_minor 12%, gc_major 9%, dyn_index 6% |

### mongodb/insert_find — 175.70M instr/iter (223.1× Node)

Top chains: JS site → runtime entry the generated code called → bucket (dominant leaf):

| instr/iter | % | JS function | JS source | runtime entry | bucket | dominant leaf |
|---:|---:|---|---|---|---|---|
| 21.18M | 12.05 | `<no JS frame>` | — | `<no JS frame>` | gc_minor | `::mark_classified` (21%) |
| 15.72M | 8.95 | `<no JS frame>` | — | `<no JS frame>` | gc_major | `::step_arena_walk` (16%) |
| 7.47M | 4.25 | `mongodb_src_cmap_wire_protocol_on_data_ts::onData$this` | mongodb/src/cmap/wire_protocol/on_data.ts:41 `const unconsumedPromises = new List<PendingPromises>();` | `js_event_emitter_on` | dyn_index | `js_array_get_f64` (60%) |
| 5.64M | 3.21 | `mongodb_src_utils_ts::List.push` | mongodb/src/utils.ts:710 `prev: this.head.prev as ListNode<T>,` | `js_object_alloc_class_inline_keys_stamped` | alloc | `object::alloc::object_alloc_class_inline_keys_impl` (61%) |
| 5.59M | 3.18 | `mongodb_src_cmap_wire_protocol_on_data_ts::10` | mongodb/src/cmap/wire_protocol/on_data.ts:113 `else unconsumedEvents.push(value);` | `js_native_call_method_by_id` | prop_lookup | `object::class_registry::dispatch::call_vtable_method_value` (35%) |
| 5.53M | 3.15 | `mongodb_src_utils_ts::List.push` | mongodb/src/utils.ts:709 `next: this.head as HeadNode<T>,` | `js_object_get_field_by_property_id_f64` | prop_lookup | `object::native_get::try_data_get_bytes` (34%) |
| 5.27M | 3.0 | `mongodb_src_utils_ts::List.shift` | mongodb/src/utils.ts:753 `return this.remove(this.head.next);` | `js_native_call_method_by_id` | prop_lookup | `object::class_registry::dispatch::call_vtable_method_value` (27%) |
| 4.84M | 2.76 | `mongodb_src_utils_ts::List.push` | mongodb/src/utils.ts:710 `prev: this.head.prev as ListNode<T>,` | `js_object_get_field_by_property_id_f64` | prop_lookup | `object::native_get::try_data_get_bytes` (41%) |
| 4.82M | 2.74 | `mongodb_src_cmap_wire_protocol_on_data_ts::10` | mongodb/src/cmap/wire_protocol/on_data.ts:111 `const promise = unconsumedPromises.shift();` | `js_native_call_method_by_id` | prop_lookup | `object::class_registry::dispatch::call_vtable_method_value` (55%) |
| 4.73M | 2.69 | `mongodb_src_utils_ts::List.push` | mongodb/src/utils.ts:714 `this.head.prev = newNode;` | `js_object_get_field_by_property_id_f64` | prop_lookup | `object::native_get::try_data_get_bytes` (45%) |
| 4.34M | 2.47 | `mongodb_src_utils_ts::List.push` | mongodb/src/utils.ts:709 `next: this.head as HeadNode<T>,` | `js_object_set_field_by_property_id` | prop_lookup | `object::shapes::shape_descriptor_by_id` (65%) |
| 3.33M | 1.9 | `mongodb_src_utils_ts::List.push` | mongodb/src/utils.ts:713 `this.head.prev.next = newNode;` | `js_object_get_field_by_property_id_f64` | prop_lookup | `object::native_get::try_data_get_bytes` (28%) |

Top inclusive runtime functions: `abi_trampoline::call_all_f64_x86_64` 64.33%; `object::class_registry::dispatch::call_vtable_method_value` 64.33%; `js_native_call_value` 63.77%; `js_native_call_method` 63.73%; `js_native_call_method_by_id` 57.26%; `node_stream::event_emitter::emit_stream_event` 53.63%; `closure::dispatch::bound::dispatch_bound_method` 53.55%; `js_run_stdlib_pump` 52.69%

Top self: `object::shapes::shape_descriptor_by_id` 7.76%; `object::native_get::try_data_get_bytes` 4.89%; `js_array_get_f64` 4.11%; `object::class_registry::dispatch::call_vtable_method_value` 3.47%; `buffer::header::is_registered_buffer_slow` 2.69%; `::mark_classified` 2.54%; `__memmove_avx512_unaligned_erms` 2.38%; `__memcmp_evex_movbe` 2.02%

Mechanism view (leaf-first): prop_lookup 35%, gc_minor 12%, gc_major 9%, dyn_index 6%, raw_alloc 5%, method_dispatch 5%
Loading
Loading