Optimize UI runtime hot paths and fix benchmark runner measurement#11613
Merged
Conversation
Driven by CPU and allocation profiles from the js-framework-benchmark-style
suite in packages/ui/bench. Every operation improved 10-76%; large subtree
teardown (the worst outlier at 4.2x slower than preact) is 4.3x faster.
- Skip per-node removeEventListener when a DOM subtree is discarded
wholesale; abort pending handler work only. The <head> node keeps full
teardown since it stays alive.
- Make component disposal allocation-free when no tasks are queued
(shared empty array + shared aborted signal).
- Store the schedule-update target as fields behind one stable closure
instead of allocating a closure per component per render.
- Fast-path mix prop normalization when the mix array is already flat,
avoiding two prop-object spreads per element per render.
- Represent keyed-diff matches as number[] instead of {oldIndex} wrapper
objects (1000 allocs per 1k-row table update).
- Dispatch diffVNodes on a single type check; curr.type === next.type is
already established, so the paired isCommittedX/isX checks were redundant.
- Replace instanceof Element/Text with null checks in isCommitted* helpers
and use indexed loops instead of for-of/destructuring in hot walks.
- Cache toKebabCase, normalizeAttributeName, and camelToKebab conversions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Replace Event Timing with in-page instrumentation for all measurements: Event Timing never reports clicks faster than 16ms and quantizes durations to 8ms buckets, so fast frameworks were measured on a coarser scale than slow ones (or timed out and killed the run entirely). Scripting is measured from a capture-phase listener to a post-flush microtask; total is captured after the frame commit (rAF + setTimeout) so style/layout/paint stay included. - Reuse a single CDP session for profiling instead of leaking one per click, and only open it when profiling is requested. (Sessions cannot be detached mid-run without breaking subsequent measurements.) - Average the two middle samples for even-length medians. - Merge saved remix results by operation so filtered runs (-b create1k) no longer wipe out comparisons for unmeasured operations, and only display previous results for operations measured in the current run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The field was never assigned anywhere — only copied between vnodes and conditionally aborted, so it was always undefined. Vestigial from the removed host-element on prop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Preview Build AvailableA preview build has been created for this PR. You can install it using: pnpm install "remix-run/remix#preview/pr-11613&path:packages/remix"This preview build will be updated automatically as you push new commits. |
Contributor
|
The preview branch |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Profile-driven performance pass over the UI runtime, plus fixes to the benchmark runner that measured it. Every operation in the packages/ui/bench suite improved 10-76% (scripting time, median of 5 runs, 4x CPU throttle); the worst outlier vs preact (large subtree teardown, 4.2x slower) is now within ~20%.
Runtime
mixprop normalization fast path avoids two prop-object spreads per element per render.number[]instead of 1000{oldIndex}wrapper objects per table update.diffVNodesdispatches on a single type check; committed-node checks drop DOMinstanceof; hot walks use indexed loops instead of for-of/destructuring.toKebabCase/normalizeAttributeName/camelToKebabconversions._controllervnode field (never assigned; vestigial from the removed host-elementonprop).Benchmark runner
Results (remix scripting ms, before → after)
🤖 Generated with Claude Code