perf(handler): reduce render lookup and scope allocations - #472
Merged
Mohamed Mansour (mohamedmansour) merged 2 commits intoAug 25, 2026
Merged
Conversation
Prepare a per-protocol render fragment index once at load so the render path resolves fragment descents, component prop names and route presence through numeric slots instead of repeating string hash lookups and sibling-route scans on every fragment. Protocol::new builds a flat index that shares the already interned fragment id Arcs and packs metadata, component prop names and route presence bits into single arenas, so large protocols are indexed without duplicating fragment graphs. Render resolves the index once per request into a small inline memo cache that spills lazily for protocols with more fragment lists than the cache holds. Output is byte-identical for buffered and streaming rendering, missing fragment and component references keep returning the same typed errors, and no public API or protobuf wire format changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Mohamed Mansour (mohamedmansour)
marked this pull request as ready for review
August 25, 2026 02:27
Mohamed Mansour (mohamedmansour)
requested review from
Bang Lee (Qusic),
Jane Chu (janechu) and
mcritzjam
August 25, 2026 02:42
Jane Chu (janechu)
approved these changes
Aug 25, 2026
Mohamed Mansour (mohamedmansour)
deleted the
mohamedmansour-perf-handler-render-fragment-index
branch
August 25, 2026 02:57
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.
Verdict
GO as one combined performance change. This PR now contains both the render-fragment index and borrowed render scopes. It consolidates and supersedes #474.
Summary
Protocol::new, replacing repeated fragment-target hashes, component prop-name conversion, and unnecessary sibling-route scans with numeric slots and flat metadata arenasunsafechangesCombined performance case
B1 alone moved work from render time to protocol construction. It improved representative fragment-heavy renders by roughly 2–5%, but added a one-time
Protocol::newcost of +24 µs for 128 fragments and +128 µs for 512 fragments, plus +3.6–5.1% retained protocol memory. It also had one measured all-miss condition case at +4.15%.B2 makes the combined stack clearly favorable. Sequential custom-allocator measurements below compare B1 with this combined B1+B2 branch on the contact-book
/contactsroute:Allocation deltas are deterministic: exactly 594, 5,724, and 57,024 allocation calls are removed at the 10/100/1,000 scales in both plugin modes. Timing is corroboration; the allocation counts are the primary evidence.
B2 does not hide B1's construction tradeoff. All 16 construction fixtures retain exactly the same bytes under B1 and combined B1+B2 (contact-book remains 102,619 bytes; the 200-route synthetic remains 574,831 bytes). The combined render savings amortize B1's largest measured construction penalty in one render at 100/1,000 contacts and in roughly six renders at 10 contacts. Completed renders retain zero additional bytes.
For the isolated B1 layer, minimum-time measurements against the original base recorded
loop_scaling/500-5.54%,plugin_fast/without_plugin-4.89%, andloop_scaling/100-4.58%. The combined measurements above then show the larger B2 gain on top of B1.Correctness
RENDER/STREAMharness lines are byte-identical between B1 and combined B1+B2component-assets path=/ plugin=trueline, which flips between the same two hashes on the original baseImplementation notes
RenderFragmentIndexreuses interned fragment IDs, stores metadata and canonical component prop names in flat arenas, tracks route presence in a bitset, and resolves fragment lists lazily through eight inline memo slots plus an on-demand spill. Render and continuation slots use the same sorted fragment-ID numbering.Ordinary rendering uses
BorrowedScope,LoopBinding, andVisibleLoopScopeto retain references into immutable request state. Four inline borrowed-scope slots cover the common component-attribute case; overflow storage and request-local pools remain bounded. Owned fallback paths preserve synthetic values and continuation lifetimes.Validation
cargo test -p microsoft-webui-handlercargo xtask check— all phases passed, including clippy, deny, workspace tests, native/WASM builds, examples, benchmark validation, and docs (using same-driveTEMP/TMPrequired by the Windows projection adapter)