Skip to content

perf(web): ssrElement joins plain children in place and walks uniform sources without resolved lists - #3486

Merged
ryansolid merged 1 commit into
nextfrom
perf/ssr-element-path
Sep 16, 2026
Merged

ryansolid merged 1 commit into
nextfrom
perf/ssr-element-path

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Part of the yak convergence work tracked in #3389 (the ssrElement element-path item).

What

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 ~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 — then ssr([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). ssrTextTail isn't touched: the parent resets it on this element's finished node either way.

The array form. sources/kinds lists were built per element even when every entry was a plain object (the normal case); an omit over a merge allocated a fill(SOURCE_OMIT) list. kinds is now the per-entry array or the one uniform kind, 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.

Numbers

Same-process round-robin against next, yak SSR lanes (solid-mprim-ssr = merged yak runtime with its writer swapped for ssrElement):

case vs next vs yak's writer
dyn-translate +21% 1.76× → 1.46×
dyn-fair +15% 1.62× → 1.41×
dyn-inline +16% 1.33× → 1.15×
multifile-composition +3%
tabs +6%

Sampled 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.

… 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-bot

changeset-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a732d2b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
test-integration Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch
@solidjs/signals Patch
solid-js Patch
@solidjs/universal Patch

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

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 35066409369

Coverage remained the same at 71.46%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1018
Covered Lines: 772
Line Coverage: 75.83%
Relevant Branches: 790
Covered Branches: 520
Branch Coverage: 65.82%
Branches in Coverage %: Yes
Coverage Strength: 14.94 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Sep 16, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 11.74%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 174 untouched benchmarks

Performance Changes

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)

Open in CodSpeed

@ryansolid
ryansolid merged commit 81ee5ca into next Sep 16, 2026
7 checks passed
@ryansolid
ryansolid deleted the perf/ssr-element-path branch September 16, 2026 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants