perf(web): ssrElement joins plain children in place and walks uniform sources without resolved lists - #3486
Merged
Conversation
… sources without resolved lists
Profiled yak's element-dense SSR cases (dyn-translate, dyn-fair,
dyn-inline) with ssrElement's array-sources form against the library's
hand-written writer: the element path cost about 3.3x the writer per
element, with everything else equal. Two structural causes.
The exit. Every element ran resolveSSRNode(children, undefined, true),
which allocates a fresh { t: [""], h: [], p: [] } result — an object and
three arrays — to append one string to, then ssr([open, close], result)
with a template array and the full hole loop to join it back. The content
most elements end up with is one string (a text child, escaped above or
by the compiler), a number, nothing, or one finished node; those now join
the open and close tags in place. Arrays and pending nodes still take the
resolver — they need it for separators and holes. The ssrTextTail marker
state is not touched: the parent resets it on this element's finished
node either way. Output is byte-identical on all fifteen yak cases and
the server suite.
The array form. sources/kinds lists were built per element even when
every entry was a plain object, the normal case, and an omit over a merge
allocated a fill(SOURCE_OMIT) list. kinds is now the per-entry array or,
when every entry is of one kind, that kind itself, so the array handed in
(or the view's entries) is walked as is. pushEntry classifies a plain
object with one `$PROXY in` check instead of two.
Same-process round-robin against next on yak's SSR lanes: dyn-translate
+21%, dyn-fair +15%, dyn-inline +16% (1.76x -> 1.46x of the writer),
multifile-composition +3%, tabs +6%; sampled allocation on dyn-translate
2959 -> 2161 bytes per instance.
Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: a732d2b The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Coverage Report for CI Build 35066409369Coverage remained the same at 71.46%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will improve performance by 11.74%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | projection derive: write one NESTED field (reference) |
2.9 ms | 2.6 ms | +11.74% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing perf/ssr-element-path (a732d2b) with next (48463f5)
7 tasks
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.
Part of the yak convergence work tracked in #3389 (the
ssrElementelement-path item).What
Profiled yak's element-dense SSR cases (
dyn-translate,dyn-fair,dyn-inline) withssrElement's array-sources form against the library's hand-written writer: the element path cost ~3.3× the writer per element, everything else equal. Two structural causes, both fixed here.The exit. Every element ran
resolveSSRNode(children, undefined, true)— a fresh{ t: [""], h: [], p: [] }result (an object and three arrays) to append one string to — thenssr([open, close], result)with a template array and the full hole loop. Children that are one string, a number, nothing, or one finished node now join the open and close tags in place. Arrays and pending nodes still take the resolver (separators, holes).ssrTextTailisn't touched: the parent resets it on this element's finished node either way.The array form.
sources/kindslists were built per element even when every entry was a plain object (the normal case); an omit over a merge allocated afill(SOURCE_OMIT)list.kindsis now the per-entry array or the one uniform kind, so the array handed in (or the view's entries) is walked as is.pushEntryclassifies a plain object with one$PROXY incheck instead of two.Numbers
Same-process round-robin against
next, yak SSR lanes (solid-mprim-ssr= merged yak runtime with its writer swapped forssrElement):nextSampled allocation on
dyn-translate: 2959 → 2161 B/instance (−27%; the writer is 1681). Output byte-identical on all 15 yak cases (verify), 969 server tests pass, plus a new test pinning every child shape against the resolver's output.Companion
The signals-side change from the same profile (omit hidden-key chain) is a separate PR so CodSpeed attributes each.