Skip to content

Initial Performance Pass#876

Draft
KetanReddy wants to merge 10 commits into
mainfrom
perf/first-pass
Draft

Initial Performance Pass#876
KetanReddy wants to merge 10 commits into
mainfrom
perf/first-pass

Conversation

@KetanReddy

@KetanReddy KetanReddy commented Jun 4, 2026

Copy link
Copy Markdown
Member

Optimizes the hot paths in core/player — data get/set, binding parse/construction,
expression evaluation, string/template resolution, and the view-resolution tree walk.
These run on every data change and view render, so per-call costs compound across a flow.

All changes preserve existing behavior and conventions, keep the file layout unchanged,
and are written to stay cheap once transpiled to ES5 for the Hermes mobile runtime
(prefer indexed loops / Object.assign / direct property access over spread, for…of,
and destructuring, which become heavier helper calls on-device).

What changed

Data model

  • data/local-model — batch set with structural sharing. A multi-binding
    transaction now clones each touched container at most once (tracked in an owned
    WeakSet) instead of re-cloning shared ancestors per binding. Single-binding sets keep
    using timm's setIn directly. The COW helper lives in data/owned-set-in.ts and
    mirrors timm's setIn exactly (intermediate creation, no-op short-circuit,
    symbol-preserving clone, progressive reads), so it's a behavior-preserving drop-in.
  • controllers/data (DataController.set)=== fast path before the deep
    dequal, indexed loops instead of reduce/map/destructuring, Set built without an
    intermediate array.

Bindings

  • BindingParser.parse reuses parserOptions directly when no overrides are passed and
    merges updates in place; BindingInstance constructor uses an indexed loop.

Expressions

  • The per-node execution context in the evaluator is built lazily, so leaf nodes
    (literals, identifiers, model refs, member access) skip the allocation.

String / template resolution

  • traverseObject no longer calls timm setIn per key (which re-cloned the whole object
    each time, ≈O(n²)); it now clones once, lazily, on first change. Hoisted the expression
    regex to a module constant and use match.index.

View resolver (computeTree)

  • Per-child folding into resolved uses the same copy-on-write setIn (shared
    data/owned-set-in.ts), cloning shared ancestors once per node instead of once per
    child. Guarded to nodes with more than one child — narrow nodes keep plain setIn
    so the WeakSet bookkeeping never becomes overhead where there's nothing to share. The
    fresh-per-node owned set guarantees the hook value / reused previousResult.value is
    never mutated (first write always clones), preserving the "unchanged → same reference"
    invariant the cache relies on.
  • Dependency set handoff — child deps are already folded into the model's read-deps,
    so the complete set is just getDependencies(); dropped the redundant per-node copy +
    re-merge and hand the (local, discarded-after) set off directly.
  • Object.assign instead of spread for the per-node options and children rebuild (cheaper
    once transpiled, behavior identical).

Benchmarks

  • Added *.bench.ts coverage for the data model, data controller, string-resolver,
    schema, binding, and an end-to-end Player.start / data-set re-resolve.
  • Broadened inputs to characterize gains across sizes: a large realistic nested structure
    for traverseObject, larger and scattered-path batch sets, larger data-controller
    batches, and a wide-node resolver view (many sibling children) that exercises the
    resolver COW path the existing deep-but-narrow view could not.

Benchmark results

Vitest bench, ops/sec, measured with controlled before/after A/Bs on the same machine.
Only changes above the ~±5% run-to-run noise floor are listed; everything else was
neutral, and there were no regressions.

Benchmark Before After Change
LocalModel set batch (15) 30.8K 114.7K +272%
DataController set batch (15, all changed) 23.8K 55.3K +133%
resolveDataRefs object — flat (30 keys) 61.2K 117.5K +92%
view resolver — data change on wide node (50 children) 6.9K 9.7K +39%
resolveDataRefs object — nested (depth 3) 238.0K 336.5K +41%
BindingParser.parse — foo.pets.1.name 1.35M 1.64M +22%
view resolver — initial resolve (wide: 50 children) 4.0K 4.7K +18%
view resolver — resolving from cache 49.8K 56.9K +14%
resolveExpressionsInString — single 2.61M 2.98M +14%
expression eval — {{foo}} = 1 + 3 1.45M 1.54M +7%
BindingParser.parse — cache hit 4.94M 5.26M +6%
DataController set — no-op / changed scalar 2.97M 3.12M +5%

The high-frequency data/binding micro-ops (single get/set, BindingInstance
construction) showed only low-single-digit gains on V8, but the loop / no-spread /
Object.assign rewrites target the ES5/Hermes output where the avoided helper calls
cost more — so on-device gains should exceed what these V8 numbers show.

Notes

  • All core/player unit tests and lint pass; no behavioral changes.
  • The batch set and resolver copy-on-write share one implementation
    (data/owned-set-in.ts) that mirrors timm's setIn semantics; both are guarded so the
    common single-write path is never penalized.
  • Numbers measured by stashing each change and re-recording the baseline on the same
    machine (the CI benchmark job compares each PR against the recorded baseline
    automatically).
  • Also includes a fix for updating the baseline metrics on main build

Change Type (required)

Indicate the type of change your pull request is:

  • patch
  • minor
  • major
  • N/A

Does your PR have any documentation updates?

  • Updated docs
  • No Update needed
  • Unable to update docs

Release Notes

Performance improvements to Player Core. For full explanation see original PR.

@KetanReddy KetanReddy requested a review from a team as a code owner June 4, 2026 19:05
@KetanReddy KetanReddy added the patch Increment the patch version when merged label Jun 4, 2026
@intuit-svc

intuit-svc commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Comparison against baseline from main. ⚠️ = regression (>10% slower), ✅ = improvement (>5% faster)

core/player ⚠️

Benchmark Current Baseline Change
core/player/src/tests/player.bench.ts > Player.start > minimal flow (cold start) 5.48K ops/s new
core/player/src/tests/player.bench.ts > Player data set > set bound data triggers re-resolve 110.74K ops/s new
core/player/src/tests/string-resolver.bench.ts > resolveExpressionsInString > single expression 717.12K ops/s new
core/player/src/tests/string-resolver.bench.ts > resolveExpressionsInString > embedded expressions 256.32K ops/s new
core/player/src/tests/string-resolver.bench.ts > resolveDataRefsInString > single ref 466.92K ops/s new
core/player/src/tests/string-resolver.bench.ts > resolveDataRefsInString > multiple refs 192.96K ops/s new
core/player/src/tests/string-resolver.bench.ts > resolveDataRefsInString > no refs (fast exit) 3.31M ops/s new
core/player/src/tests/string-resolver.bench.ts > resolveDataRefs object > flat (30 keys) 28.46K ops/s new
core/player/src/tests/string-resolver.bench.ts > resolveDataRefs object > nested (depth 3) 75.46K ops/s new
core/player/src/tests/string-resolver.bench.ts > resolveDataRefs object > large nested (8 sections x 10 fields, depth 5) 1.47K ops/s new
core/player/src/binding/tests/binding.bench.ts > BindingInstance construction > from array (numeric + string segments) 1.15M ops/s new
core/player/src/binding/tests/binding.bench.ts > BindingInstance construction > from string 881.47K ops/s new
core/player/src/binding/tests/binding.bench.ts > BindingParser.parse (simple) > cache hit 1.04M ops/s new
core/player/src/binding/tests/binding.bench.ts > BindingParser.parse (simple) > cache cold (fast-path) 554.81K ops/s new
core/player/src/binding/tests/binding.bench.ts > BindingParser.parse (complex query) > foo.pets[name='frodo'].type 349.77K ops/s new
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.bar 621.22K ops/s 792.61K ops/s -21.6% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets.1.name 399.69K ops/s 447.84K ops/s -10.8% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets.01.name 462.12K ops/s 452.53K ops/s +2.1%
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets['01'].name 342.73K ops/s 461.73K ops/s -25.8% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets[01].name 286.89K ops/s 464.71K ops/s -38.3% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets[name = "frodo"].type 168.14K ops/s 214.51K ops/s -21.6% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["name" = "sprinkles"].type 218.65K ops/s 189.71K ops/s +15.3% ✅
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["isDog" = false].type 193.69K ops/s 271.18K ops/s -28.6% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["isDog" = true].type 166.70K ops/s 228.34K ops/s -27.0% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.bar 357.45K ops/s 484.95K ops/s -26.3% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets.1.name 357.58K ops/s 259.12K ops/s +38.0% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets.01.name 384.53K ops/s 279.20K ops/s +37.7% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets['01'].name 314.75K ops/s 285.38K ops/s +10.3% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets[01].name 346.43K ops/s 363.01K ops/s -4.6%
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets[name = "frodo"].type 173.96K ops/s 243.94K ops/s -28.7% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["name" = "sprinkles"].type 131.62K ops/s 200.91K ops/s -34.5% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["isDog" = false].type 162.05K ops/s 237.34K ops/s -31.7% ⚠️
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["isDog" = true].type 173.17K ops/s 230.38K ops/s -24.8% ⚠️
core/player/src/data/tests/local-model.bench.ts > LocalModel get > get shallow 2.41M ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel get > get deep 2.05M ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel get > get array index 2.10M ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel set > set existing shallow 378.36K ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel set > set deep 289.57K ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel set > set new key 337.81K ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel set > set batch (15) 28.24K ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel set > set batch (50, shared parent) 9.71K ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel set > set batch (7, scattered paths) 50.73K ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel delete > delete object key 72.02K ops/s new
core/player/src/data/tests/local-model.bench.ts > LocalModel delete > delete array index 235.91K ops/s new
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = 1 + 3 (sync) 291.12K ops/s 431.98K ops/s -32.6% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = 1 + 3 (async) 260.56K ops/s 256.47K ops/s +1.6%
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: conditional(true, true, false) (sync) 367.59K ops/s 446.50K ops/s -17.7% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: conditional(true, true, false) (async) 344.86K ops/s 411.46K ops/s -16.2% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional({{bar}} > 0, true, false) (sync) 147.94K ops/s 187.69K ops/s -21.2% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional({{bar}} > 0, true, false) (async) 133.88K ops/s 176.75K ops/s -24.3% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(conditional(true = false, false, true), conditional(false = false, true, false), conditional(true = true, false, true)) (sync) 109.62K ops/s 153.94K ops/s -28.8% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(conditional(true = false, false, true), conditional(false = false, true, false), conditional(true = true, false, true)) (async) 97.94K ops/s 146.44K ops/s -33.1% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = await(asyncTestFunction(1)) (sync) N/A N/A N/A
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = await(asyncTestFunction(1)) (async) 234.46K ops/s 274.66K ops/s -14.6% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = asyncTestFunction(1) (sync) 200.22K ops/s 304.58K ops/s -34.3% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = asyncTestFunction(1) (async) 178.08K ops/s 250.35K ops/s -28.9% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: asyncTestFunction(1) (sync) 499.55K ops/s 802.33K ops/s -37.7% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: asyncTestFunction(1) (async) 410.18K ops/s 571.23K ops/s -28.2% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(!{{bar}} == false, await(asyncTestFunction(1)), false) (sync) 114.11K ops/s 155.77K ops/s -26.7% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(!{{bar}} == false, await(asyncTestFunction(1)), false) (async) 95.94K ops/s 128.38K ops/s -25.3% ⚠️
core/player/src/schema/tests/schema.bench.ts > SchemaController getType (uncached binding) > shallow binding 379.72K ops/s new
core/player/src/schema/tests/schema.bench.ts > SchemaController getType (uncached binding) > deep binding (array+record, 6 segments) 105.38K ops/s new
core/player/src/schema/tests/schema.bench.ts > SchemaController getApparentType (cached binding) > repeat same binding 2.58M ops/s new
core/player/src/controllers/data/tests/controller.bench.ts > DataController set > set no-op 584.24K ops/s new
core/player/src/controllers/data/tests/controller.bench.ts > DataController set > set changed scalar 312.59K ops/s new
core/player/src/controllers/data/tests/controller.bench.ts > DataController set > set changed object 292.20K ops/s new
core/player/src/controllers/data/tests/controller.bench.ts > DataController set > set batch (15, all changed) 11.84K ops/s new
core/player/src/controllers/data/tests/controller.bench.ts > DataController set > set batch (50, all changed) 4.28K ops/s new
core/player/src/controllers/data/tests/controller.bench.ts > DataController get > get hot 1.81M ops/s new
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > initial resolve 451.17 ops/s 611.80 ops/s -26.3% ⚠️
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > Resolving from cache 11.03K ops/s 18.52K ops/s -40.5% ⚠️
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > data changes 2.23K ops/s 2.99K ops/s -25.5% ⚠️
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > data changes slow 429.98 ops/s 635.20 ops/s -32.3% ⚠️
core/player/src/view/resolver/tests/index.bench.ts > resolver wide-node benchmarks > initial resolve (wide: 50 children) 1.34K ops/s new
core/player/src/view/resolver/tests/index.bench.ts > resolver wide-node benchmarks > data change on wide node (50 children) 2.13K ops/s new

plugins/async-node/core ⚠️

Benchmark Current Baseline Change
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 1 times 7.98K ops/s 14.01K ops/s -43.0% ⚠️
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 5 times 7.28K ops/s 12.49K ops/s -41.7% ⚠️
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 10 times 8.88K ops/s 10.49K ops/s -15.3% ⚠️
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 50 times 2.59K ops/s 3.25K ops/s -20.3% ⚠️
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 100 times 1.41K ops/s 1.78K ops/s -20.8% ⚠️
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 1 times 4.73K ops/s 5.91K ops/s -19.9% ⚠️
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 5 times 5.23K ops/s 7.91K ops/s -33.9% ⚠️
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 10 times 4.65K ops/s 6.92K ops/s -32.8% ⚠️
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 50 times 1.62K ops/s 2.60K ops/s -37.7% ⚠️
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 100 times 1.07K ops/s 1.50K ops/s -28.6% ⚠️

react/player

Benchmark Current Baseline Change
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 1 ReactAssets 634.42 ops/s 532.19 ops/s +19.2% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 1 ReactAssets 1.14K ops/s 742.64 ops/s +53.5% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 5 ReactAssets 637.99 ops/s 529.03 ops/s +20.6% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 5 ReactAssets 1.14K ops/s 787.69 ops/s +44.4% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 10 ReactAssets 653.61 ops/s 530.01 ops/s +23.3% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 10 ReactAssets 924.25 ops/s 701.94 ops/s +31.7% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 50 ReactAssets 489.98 ops/s 426.81 ops/s +14.8% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 50 ReactAssets 266.27 ops/s 237.77 ops/s +12.0% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 100 ReactAssets 420.35 ops/s 329.70 ops/s +27.5% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 100 ReactAssets 126.77 ops/s 78.58 ops/s +61.3% ✅

@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 38.68kB (0.75%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
plugins/check-path/core 446.27kB 3.67kB (0.83%) ⬆️
plugins/async-node/core 507.93kB 3.67kB (0.73%) ⬆️
plugins/markdown/core 686.93kB 3.67kB (0.54%) ⬆️
plugins/reference-assets/core 558.58kB 3.67kB (0.66%) ⬆️
plugins/beacon/core 427.87kB 3.67kB (0.87%) ⬆️
plugins/metrics/core 464.43kB 3.67kB (0.8%) ⬆️
core/player 1.03MB 12.98kB (1.27%) ⬆️
plugins/external-state/core 430.54kB 3.67kB (0.86%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: plugins/check-path/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
CheckPathPlugin.native.js 3.67kB 416.79kB 0.89%
view changes for bundle: plugins/reference-assets/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
ReferenceAssetsPlugin.native.js 3.67kB 520.59kB 0.71%
view changes for bundle: plugins/markdown/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
MarkdownPlugin.native.js 3.67kB 661.78kB 0.56%
view changes for bundle: core/player

Assets Changed:

Asset Name Size Change Total Size Change (%)
Player.native.js 3.98kB 430.32kB 0.93%
cjs/index.cjs 3.04kB 205.32kB 1.5%
index.legacy-esm.js 2.98kB 198.53kB 1.52%
index.mjs 2.98kB 198.53kB 1.52%
view changes for bundle: plugins/external-state/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
ExternalStatePlugin.native.js 3.67kB 411.57kB 0.9%
view changes for bundle: plugins/async-node/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
AsyncNodePlugin.native.js 3.67kB 445.3kB 0.83%
view changes for bundle: plugins/beacon/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
BeaconPlugin.native.js 3.67kB 413.4kB 0.9%
view changes for bundle: plugins/metrics/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
MetricsPlugin.native.js 3.67kB 432.09kB 0.86%

@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (c96e810) to head (e88015a).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@     Coverage Diff     @@
##   main   #876   +/-   ##
===========================
===========================

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@KetanReddy

Copy link
Copy Markdown
Member Author

Ran the JVM perf tests locally because we don't have them set up in CI yet and here are the results

Benchmark Runtime Mode Score (base) Error (base) Score (PR) Error (PR) Units Improvement
BenchPlayerCreation.createHeadlessPlayer Hermes avgt 4.164 0.476 5.327 0.694 ms/op 127.93%
BenchPlayerCreation.createHeadlessPlayer J2V8 avgt 12.176 1.084 13.076 0.739 ms/op 107.39%
BenchPlayerCreation.createJSPlayer Hermes avgt 14.834 0.240 15.499 1.487 ms/op 104.48%
BenchPlayerCreation.createJSPlayer J2V8 avgt 8.507 0.081 9.559 0.582 ms/op 112.37%
BenchPlayerFlow.firstViewUpdate Hermes avgt 6.899 0.291 8.742 0.919 ms/op 126.71%
BenchPlayerFlow.firstViewUpdate J2V8 avgt 16.443 0.655 19.274 0.669 ms/op 117.22%
BenchRuntimeCreation.createHeadlessPlayer Hermes avgt 4.851 0.843 6.383 0.565 ms/op 131.58%
BenchRuntimeCreation.createHeadlessPlayer J2V8 avgt 13.294 1.338 15.605 0.631 ms/op 117.38%
BenchRuntimeCreation.createRuntime Hermes avgt 0.676 0.016 0.755 0.017 ms/op 111.69%
BenchRuntimeCreation.createRuntime J2V8 avgt 0.712 0.040 0.776 0.236 ms/op 108.99%

@KVSRoyal KVSRoyal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks fine. Might be helpful to add more a few more comments flagging where things have been done one way for performance to prevent unintended changes later.

) {
const split = Array.isArray(raw) ? raw : raw.split(".");
this.split = split.map((segment) => {
const normalized: RawBindingSegment[] = new Array(split.length);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking it might be helpful to drop a comment here that doing the manual for-loop is for better performance. Might help prevent this accidentally getting changed later. Thoughts?

@@ -0,0 +1,95 @@
import { bench, describe } from "vitest";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this line; just making reply-able:

  1. Is this PR targeting memory performance only? (Not speed?)

  2. Are the before and after flipped in your table? Because this table is showing that everything has a significant increase in memory, which seems unintended?

    Benchmark Before After Change
    LocalModel set batch (15) 30.8K 114.7K +272%

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its only looking at performance right now because we don't currently have a good memory profiling strategy. So the benchmarks are in terms of ops/s so its execution of speedup, not memory.

@KetanReddy KetanReddy marked this pull request as draft June 29, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Increment the patch version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants