From 5791fc3edc36bfdcd5e361551c17d0396850f033 Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Wed, 2 Sep 2026 12:09:31 -0700 Subject: [PATCH 1/4] perf: borrow protocol-owned names in the render context The render context materialized data it only ever reads from the protocol. `route_children` held `Vec`, so descending into a matched route deep-cloned the entire remaining route subtree - two strings, the recursive children vector, and the cache-tag and invalidation vectors, for every route on the matched chain of every request. The scope maps, the rendered-component set, and the document-style set were all `String`-keyed even though every key they receive is a component prop name, a `` moniker, or a style resource name that the protocol already owns and that outlives the request. Those fields now hold `Cow<'protocol, _>`, so a render binds names and descends route levels without copying a byte. The owned variant is not removed because a suspended streaming continuation outlives the borrow it rendered against: `SessionCore` detaches what it parks between host calls, which moves every entry and allocates only for names that render newly bound - the same allocation the `String`-keyed map used to pay on insert, just deferred to the suspension that actually needs it. Keeping the owned variant is also what keeps the change safe. The workspace denies `unsafe_code`, and `StreamingSession` owns its `Arc`, so a context lifetime threaded into the session state would have no caller to name it and would need a self-referential struct. `Cow` lets the render path borrow while the session parks something that stands on its own, so the session, the FFI, and the WASM and Python bindings are untouched. An outlet still consumes its route level rather than restoring it, which preserves the previous behavior exactly. | Path | Before | After | Change | |---|---:|---:|---:| | string allocs/run | 141 | 122 | -13.5% | | streaming allocs/run | 155 | 136 | -12.3% | | streaming POOLED allocs/run | 148 | 129 | -12.8% | | string bytes/run | 30.5 KiB | 28.7 KiB | -5.9% | | streaming POOLED bytes/run | 8.7 KiB | 6.9 KiB | -20.7% | | string wall us/run @1000 | 13.60 | 13.22 | -2.8% | | Contact book Render/1000 P50 | 1.52 ms | 1.48 ms | -2.6% | | Output size | 24152 B | 24152 B | unchanged | The contact-book fixture has a shallow route graph, so it understates the route-tree win. On the three-level nested `examples/app/routes` tree, allocations per render drop 58 -> 48 at `/` and 141 -> 128 at `/sections/:id/topics/:id/lessons/:id`, with byte-identical output at every depth. Validation: `cargo xtask check`, `cargo xtask bench streaming-resource`, `cargo bench -p microsoft-webui --bench contact_book_bench`. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2edb7e98-5706-46bc-b191-51620cd290e5 --- crates/webui-handler/src/lib.rs | 222 ++++++++++++------ crates/webui-handler/src/route_handler.rs | 4 +- .../webui-handler/src/streaming/inventory.rs | 2 +- crates/webui-handler/src/streaming/session.rs | 48 ++-- crates/webui-handler/src/streaming/vm.rs | 36 ++- 5 files changed, 215 insertions(+), 97 deletions(-) diff --git a/crates/webui-handler/src/lib.rs b/crates/webui-handler/src/lib.rs index e9bc7103..89287bb6 100644 --- a/crates/webui-handler/src/lib.rs +++ b/crates/webui-handler/src/lib.rs @@ -475,10 +475,50 @@ impl VisibleLoopScope { #[derive(Clone, Copy)] struct LocalValueSources<'ctx, 'protocol, 'state> { - owned: &'ctx HashMap, + owned: &'ctx ScopeMap<'protocol>, borrowed: &'ctx BorrowedScope<'protocol, 'state>, } +/// Name a render context binds a value or a render decision to. +/// +/// Every name the handler records originates in the protocol, so the borrowed +/// variant is what a render actually uses. The owned variant exists only for a +/// suspended streaming continuation, whose parked scope outlives the borrow it +/// was rendered from. +pub(crate) type Name<'protocol> = Cow<'protocol, str>; + +/// Scope bindings materialized for one component or `` body. +pub(crate) type ScopeMap<'protocol> = HashMap, Value>; + +/// Set of protocol names visited during a render. +pub(crate) type NameSet<'protocol> = HashSet>; + +/// Route level an `` matches against. +pub(crate) type RouteChildren<'protocol> = Cow<'protocol, [webui_protocol::WebUiFragmentRoute]>; + +/// Detach a scope map from the borrow it was rendered against. +/// +/// Entries the render materialized are moved, not copied; only names still +/// pointing into the protocol pay an allocation, and they pay it once because +/// the result stays owned from then on. `target` keeps its bucket capacity, so +/// a session that parks a scope on every suspension reuses one map. +pub(crate) fn absorb_scope_map(target: &mut ScopeMap<'static>, source: ScopeMap<'_>) { + target.clear(); + target.reserve(source.len()); + for (name, value) in source { + target.insert(Cow::Owned(name.into_owned()), value); + } +} + +/// Detach a name set from the borrow it was rendered against. +pub(crate) fn absorb_name_set(target: &mut NameSet<'static>, source: NameSet<'_>) { + target.clear(); + target.reserve(source.len()); + for name in source { + target.insert(Cow::Owned(name.into_owned())); + } +} + #[derive(Default)] struct BorrowedScope<'protocol, 'state> { inline: [Option<(&'protocol str, &'state Value)>; INLINE_SCOPE_SLOTS], @@ -556,12 +596,12 @@ impl<'protocol, 'state> BorrowedScope<'protocol, 'state> { self.overflow.clear(); } - fn clone_into_owned(&self, target: &mut HashMap) { + fn clone_into_owned(&self, target: &mut ScopeMap<'protocol>) { for (name, value) in self.inline[..self.inline_len].iter().flatten() { - target.insert((*name).to_owned(), (*value).clone()); + target.insert(Cow::Borrowed(name), (*value).clone()); } for (name, value) in &self.overflow { - target.insert((*name).to_owned(), (*value).clone()); + target.insert(Cow::Borrowed(name), (*value).clone()); } } } @@ -825,6 +865,44 @@ fn component_attr_source(attribute: &webui_protocol::WebUIFragmentAttribute) -> } } +/// Component a route level hosts at `index`, matching the level's own ownership. +fn host_component_at<'protocol>( + children: &RouteChildren<'protocol>, + index: usize, +) -> Name<'protocol> { + match children { + Cow::Borrowed(routes) => match routes.get(index) { + Some(route) => Cow::Borrowed(&route.fragment_id), + None => Cow::Borrowed(""), + }, + Cow::Owned(routes) => match routes.get(index) { + Some(route) => Cow::Owned(route.fragment_id.clone()), + None => Cow::Borrowed(""), + }, + } +} + +/// Route level one step below `children[index]`. +/// +/// A borrowed level yields a borrowed sublevel, so descending through a route +/// tree during a render never copies a `WebUiFragmentRoute`. Only a level a +/// streaming continuation already materialized is cloned. +fn descend_into<'protocol>( + children: &RouteChildren<'protocol>, + index: usize, +) -> RouteChildren<'protocol> { + match children { + Cow::Borrowed(routes) => match routes.get(index) { + Some(route) => Cow::Borrowed(&route.children), + None => Cow::Borrowed(&[]), + }, + Cow::Owned(routes) => match routes.get(index) { + Some(route) => Cow::Owned(route.children.clone()), + None => Cow::Borrowed(&[]), + }, + } +} + /// Fragment ID a fragment descends into, or `None` when it renders inline. fn fragment_target_id(fragment: &WebUIFragment) -> Option<&str> { match fragment.fragment.as_ref()? { @@ -852,7 +930,7 @@ pub(crate) struct WebUIProcessContext<'protocol, 'state, 'output> { pub(crate) component_asset_style_links: &'protocol str, pub(crate) state: &'state Value, pub(crate) writer: &'output mut dyn ResponseWriter, - pub(crate) local_vars: HashMap, + pub(crate) local_vars: ScopeMap<'protocol>, /// Component-local values that still point into immutable request state. local_borrowed_vars: BorrowedScope<'protocol, 'state>, /// Borrowed loop bindings, in lexical order. @@ -861,7 +939,7 @@ pub(crate) struct WebUIProcessContext<'protocol, 'state, 'output> { /// bodies hide outer loop monikers while still allowing their own loops. visible_loop_scope: VisibleLoopScope, /// Accumulates component attribute values between attrStart and the component fragment. - pub(crate) component_attrs: HashMap, + pub(crate) component_attrs: ScopeMap<'protocol>, /// State-backed component attributes accumulated without cloning. component_borrowed_attrs: BorrowedScope<'protocol, 'state>, /// True only while parser-produced component opening-tag attributes are @@ -879,12 +957,17 @@ pub(crate) struct WebUIProcessContext<'protocol, 'state, 'output> { /// Component names visited during rendering (for selective f-template emission /// and CSS module dedup — only the first render of each component emits /// its `