perf(start-client-core): O(1) buffer drain in client frame decoder - #7663

Closed
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer
Closed

perf(start-client-core): O(1) buffer drain in client frame decoder#7663
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer

Conversation

@anonrig

@anonriganonrig commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Replace the O(n) bufferList.shift() in the client-side frame decoder (packages/start-client-core/src/client-rpc/frame-decoder.ts) with an O(1) head pointer.

extractFlattened() dropped each fully-consumed chunk from the front of bufferList with shift(). When a single large frame (e.g. a big RawStream payload) is assembled from many small network reads, the extract loop calls shift() once per chunk — and each shift() re-indexes the whole array, so reassembly degrades to O(n²).

This PR tracks the first un-consumed chunk with a bufferHead index and advances it in O(1) instead of shifting. readHeader() reads from bufferHead as well. Consumed slots are released for GC, and the array is compacted:

  • fully drained (bufferHead === bufferList.length) → reset in O(1) (the common terminal state), or
  • once the consumed prefix grows past a small threshold → splice() it off (amortized O(1) per consumed chunk).

This mirrors the index-pointer approach already used in transformStreamWithRouter for the same reason.

Why

Same hot path as the sibling zero-copy PR: decoding streamed server-function responses and RawStream payloads. The O(n²) bites specifically when one frame spans many buffered chunks.

Standalone micro-benchmark (Node, median of 12 runs), draining N buffered chunks:

Chunksshift()head pointerSpeedup
20026.7 ms2.5 ms10.6x
1000173 ms15 ms11.5x

Tests

  • Existing frame-decoder suite passes.
  • Added two tests for the changed paths:
    • a 200-byte CHUNK payload delivered one byte at a time (forces the header slow path + many whole-chunk consumptions + the fully-drained reset),
    • 100-byte frames fed in 7-byte reads that never align with frame boundaries, so the head pointer climbs past the compaction threshold repeatedly (exercises the splice() prefix drop).
nx run @tanstack/start-client-core:test:unit -> 20 passed
nx run @tanstack/start-client-core:test:types -> no errors

Notes

  • Independent of the sibling zero-copy PR. Both touch extractFlattened, so whichever merges second will need a trivial rebase.
  • Includes a changeset (@tanstack/start-client-core patch).

Summary by CodeRabbit

  • Performance

    • Optimized frame decoder to more efficiently handle large frames when data arrives as multiple small reads, reducing processing overhead.
  • Tests

    • Added test coverage for frame decoder with fragmented input streams and non-aligned chunk boundaries.

@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

createFrameDecoder in frame-decoder.ts now tracks the first unconsumed chunk via a bufferHead index instead of calling bufferList.shift(). Both paths of readHeader and the extractFlattened function are updated to use this pointer, with periodic list compaction. Two new tests cover one-byte-at-a-time and 7-byte-misaligned delivery, and a patch changeset entry is added.

Changes

Frame decoder O(1) buffer optimization

Layer / File(s)Summary
bufferHead pointer and extractFlattened rewrite
packages/start-client-core/src/client-rpc/frame-decoder.ts
Adds bufferHead to the parsing loop. Updates readHeader's fast path to read from bufferList[bufferHead] and its slow-path loop to iterate from bufferHead. Rewrites extractFlattened to advance bufferHead on full chunk consumption, replace partially consumed chunks with subarrays, and splice the consumed prefix after a compaction threshold.
Slow-path and misaligned-chunk tests, changeset
packages/start-client-core/tests/frame-decoder.test.ts, .changeset/perf-frame-decoder-index-pointer.md
Adds a test that feeds a CHUNK payload one byte per enqueue and reassembles it, adds a test that feeds multiple JSON frames in 7-byte misaligned reads and verifies ordered decoding, and records a patch changeset entry for the optimization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more shifting the pile — just a pointer so neat,
I hop to the head where the fresh chunks I meet.
O(n²) once loomed like a very tall stack,
Now O(1) each step, no slow tumble back.
The frames decode swiftly, the buffers stay trim —
A rabbit's best run, fast and light on a whim! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely captures the main performance optimization: replacing an O(n) shift operation with an O(1) head pointer approach in the client frame decoder.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/start-client-core/tests/frame-decoder.test.ts (1)

567-567: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use braces for one-line control statements in tests.

Line 567, Line 600-601, and Line 641 use single-line bodies without braces, which violates the project rule for TS/JS control flow.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

Suggested patch
- for (let i = 0; i < payload.length; i++) payload[i] = (i * 7) % 256+ for (let i = 0; i < payload.length; i++) {+ payload[i] = (i * 7) % 256+ }
while (true) {
const { done, value } = await rawReader.read()
- if (done) break- if (value) received.push(...value)+ if (done) {+ break+ }+ if (value) {+ received.push(...value)+ }
}
while (true) {
const { done, value } = await reader.read()
- if (done) break+ if (done) {+ break+ }
received.push(value)
}

Also applies to: 600-601, 641-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-client-core/tests/frame-decoder.test.ts` at line 567, The for
loop on line 567 with body payload[i] = (i * 7) % 256 uses a single-line body
without braces, violating the project's coding guidelines. Add curly braces
around the loop body to properly format it. Additionally, apply the same fix to
the other single-line control statements mentioned at lines 600-601 and 641 by
wrapping their bodies in curly braces.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/start-client-core/tests/frame-decoder.test.ts`:
- Line 567: The for loop on line 567 with body payload[i] = (i * 7) % 256 uses a
single-line body without braces, violating the project's coding guidelines. Add
curly braces around the loop body to properly format it. Additionally, apply the
same fix to the other single-line control statements mentioned at lines 600-601
and 641 by wrapping their bodies in curly braces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9195414-0588-45d1-b334-36aea734698b

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 38c9d48.

📒 Files selected for processing (3)
  • .changeset/perf-frame-decoder-index-pointer.md
  • packages/start-client-core/src/client-rpc/frame-decoder.ts
  • packages/start-client-core/tests/frame-decoder.test.ts

@nx-cloud

nx-cloudBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 38c9d48

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded14mView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 18sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-22 11:15:39 UTC

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7663

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7663

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7663

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7663

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7663

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7663

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7663

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7663

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7663

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7663

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7663

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7663

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7663

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7663

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7663

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7663

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7663

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7663

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7663

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7663

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7663

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7663

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7663

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7663

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7663

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7663

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7663

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7663

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7663

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7663

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7663

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7663

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7663

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7663

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7663

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7663

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7663

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7663

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7663

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7663

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7663

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7663

commit: 38c9d48

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️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
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem serialization-payload (solid)6.9 MB7.4 MB-5.96%
Memorymem aborted-requests (solid)1.8 MB1.6 MB+12.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing anonrig:perf/frame-decoder-index-pointer (38c9d48) with main (f23ed0f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (279a849) during the generation of this report, so f23ed0f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Sheraff

Copy link
Copy Markdown
Collaborator

Can you fix the conflicts on this one? And then I'll merge it

@Sheraff

Copy link
Copy Markdown
Collaborator

GitHub could not update this private-fork branch because of the conflict with #7662, so I resolved it on current main in replacement PR #8009. The original commit authorship is preserved there.

@anonriganonrig closed this Aug 16, 2026
@anonrig
anonrigforce-pushed the perf/frame-decoder-index-pointer branch from 38c9d48 to f97188fCompareAugust 16, 2026 16:39
Sign up for freeto 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

@anonrig@Sheraff
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

perf(start-client-core): O(1) buffer drain in client frame decoder - #7663

Closed
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer
Closed

perf(start-client-core): O(1) buffer drain in client frame decoder#7663
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer

Conversation

@anonrig

@anonriganonrig commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Replace the O(n) bufferList.shift() in the client-side frame decoder (packages/start-client-core/src/client-rpc/frame-decoder.ts) with an O(1) head pointer.

extractFlattened() dropped each fully-consumed chunk from the front of bufferList with shift(). When a single large frame (e.g. a big RawStream payload) is assembled from many small network reads, the extract loop calls shift() once per chunk — and each shift() re-indexes the whole array, so reassembly degrades to O(n²).

This PR tracks the first un-consumed chunk with a bufferHead index and advances it in O(1) instead of shifting. readHeader() reads from bufferHead as well. Consumed slots are released for GC, and the array is compacted:

  • fully drained (bufferHead === bufferList.length) → reset in O(1) (the common terminal state), or
  • once the consumed prefix grows past a small threshold → splice() it off (amortized O(1) per consumed chunk).

This mirrors the index-pointer approach already used in transformStreamWithRouter for the same reason.

Why

Same hot path as the sibling zero-copy PR: decoding streamed server-function responses and RawStream payloads. The O(n²) bites specifically when one frame spans many buffered chunks.

Standalone micro-benchmark (Node, median of 12 runs), draining N buffered chunks:

Chunksshift()head pointerSpeedup
20026.7 ms2.5 ms10.6x
1000173 ms15 ms11.5x

Tests

  • Existing frame-decoder suite passes.
  • Added two tests for the changed paths:
    • a 200-byte CHUNK payload delivered one byte at a time (forces the header slow path + many whole-chunk consumptions + the fully-drained reset),
    • 100-byte frames fed in 7-byte reads that never align with frame boundaries, so the head pointer climbs past the compaction threshold repeatedly (exercises the splice() prefix drop).
nx run @tanstack/start-client-core:test:unit -> 20 passed
nx run @tanstack/start-client-core:test:types -> no errors

Notes

  • Independent of the sibling zero-copy PR. Both touch extractFlattened, so whichever merges second will need a trivial rebase.
  • Includes a changeset (@tanstack/start-client-core patch).

Summary by CodeRabbit

  • Performance

    • Optimized frame decoder to more efficiently handle large frames when data arrives as multiple small reads, reducing processing overhead.
  • Tests

    • Added test coverage for frame decoder with fragmented input streams and non-aligned chunk boundaries.

@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

createFrameDecoder in frame-decoder.ts now tracks the first unconsumed chunk via a bufferHead index instead of calling bufferList.shift(). Both paths of readHeader and the extractFlattened function are updated to use this pointer, with periodic list compaction. Two new tests cover one-byte-at-a-time and 7-byte-misaligned delivery, and a patch changeset entry is added.

Changes

Frame decoder O(1) buffer optimization

Layer / File(s)Summary
bufferHead pointer and extractFlattened rewrite
packages/start-client-core/src/client-rpc/frame-decoder.ts
Adds bufferHead to the parsing loop. Updates readHeader's fast path to read from bufferList[bufferHead] and its slow-path loop to iterate from bufferHead. Rewrites extractFlattened to advance bufferHead on full chunk consumption, replace partially consumed chunks with subarrays, and splice the consumed prefix after a compaction threshold.
Slow-path and misaligned-chunk tests, changeset
packages/start-client-core/tests/frame-decoder.test.ts, .changeset/perf-frame-decoder-index-pointer.md
Adds a test that feeds a CHUNK payload one byte per enqueue and reassembles it, adds a test that feeds multiple JSON frames in 7-byte misaligned reads and verifies ordered decoding, and records a patch changeset entry for the optimization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more shifting the pile — just a pointer so neat,
I hop to the head where the fresh chunks I meet.
O(n²) once loomed like a very tall stack,
Now O(1) each step, no slow tumble back.
The frames decode swiftly, the buffers stay trim —
A rabbit's best run, fast and light on a whim! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely captures the main performance optimization: replacing an O(n) shift operation with an O(1) head pointer approach in the client frame decoder.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/start-client-core/tests/frame-decoder.test.ts (1)

567-567: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use braces for one-line control statements in tests.

Line 567, Line 600-601, and Line 641 use single-line bodies without braces, which violates the project rule for TS/JS control flow.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

Suggested patch
- for (let i = 0; i < payload.length; i++) payload[i] = (i * 7) % 256+ for (let i = 0; i < payload.length; i++) {+ payload[i] = (i * 7) % 256+ }
while (true) {
const { done, value } = await rawReader.read()
- if (done) break- if (value) received.push(...value)+ if (done) {+ break+ }+ if (value) {+ received.push(...value)+ }
}
while (true) {
const { done, value } = await reader.read()
- if (done) break+ if (done) {+ break+ }
received.push(value)
}

Also applies to: 600-601, 641-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-client-core/tests/frame-decoder.test.ts` at line 567, The for
loop on line 567 with body payload[i] = (i * 7) % 256 uses a single-line body
without braces, violating the project's coding guidelines. Add curly braces
around the loop body to properly format it. Additionally, apply the same fix to
the other single-line control statements mentioned at lines 600-601 and 641 by
wrapping their bodies in curly braces.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/start-client-core/tests/frame-decoder.test.ts`:
- Line 567: The for loop on line 567 with body payload[i] = (i * 7) % 256 uses a
single-line body without braces, violating the project's coding guidelines. Add
curly braces around the loop body to properly format it. Additionally, apply the
same fix to the other single-line control statements mentioned at lines 600-601
and 641 by wrapping their bodies in curly braces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9195414-0588-45d1-b334-36aea734698b

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 38c9d48.

📒 Files selected for processing (3)
  • .changeset/perf-frame-decoder-index-pointer.md
  • packages/start-client-core/src/client-rpc/frame-decoder.ts
  • packages/start-client-core/tests/frame-decoder.test.ts

@nx-cloud

nx-cloudBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 38c9d48

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded14mView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 18sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-22 11:15:39 UTC

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7663

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7663

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7663

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7663

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7663

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7663

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7663

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7663

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7663

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7663

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7663

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7663

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7663

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7663

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7663

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7663

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7663

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7663

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7663

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7663

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7663

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7663

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7663

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7663

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7663

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7663

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7663

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7663

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7663

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7663

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7663

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7663

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7663

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7663

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7663

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7663

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7663

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7663

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7663

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7663

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7663

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7663

commit: 38c9d48

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️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
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem serialization-payload (solid)6.9 MB7.4 MB-5.96%
Memorymem aborted-requests (solid)1.8 MB1.6 MB+12.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing anonrig:perf/frame-decoder-index-pointer (38c9d48) with main (f23ed0f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (279a849) during the generation of this report, so f23ed0f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Sheraff

Copy link
Copy Markdown
Collaborator

Can you fix the conflicts on this one? And then I'll merge it

@Sheraff

Copy link
Copy Markdown
Collaborator

GitHub could not update this private-fork branch because of the conflict with #7662, so I resolved it on current main in replacement PR #8009. The original commit authorship is preserved there.

@anonriganonrig closed this Aug 16, 2026
@anonrig
anonrigforce-pushed the perf/frame-decoder-index-pointer branch from 38c9d48 to f97188fCompareAugust 16, 2026 16:39
Sign up for freeto 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

@anonrig@Sheraff
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

perf(start-client-core): O(1) buffer drain in client frame decoder - #7663

Closed
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer
Closed

perf(start-client-core): O(1) buffer drain in client frame decoder#7663
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer

Conversation

@anonrig

@anonriganonrig commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Replace the O(n) bufferList.shift() in the client-side frame decoder (packages/start-client-core/src/client-rpc/frame-decoder.ts) with an O(1) head pointer.

extractFlattened() dropped each fully-consumed chunk from the front of bufferList with shift(). When a single large frame (e.g. a big RawStream payload) is assembled from many small network reads, the extract loop calls shift() once per chunk — and each shift() re-indexes the whole array, so reassembly degrades to O(n²).

This PR tracks the first un-consumed chunk with a bufferHead index and advances it in O(1) instead of shifting. readHeader() reads from bufferHead as well. Consumed slots are released for GC, and the array is compacted:

  • fully drained (bufferHead === bufferList.length) → reset in O(1) (the common terminal state), or
  • once the consumed prefix grows past a small threshold → splice() it off (amortized O(1) per consumed chunk).

This mirrors the index-pointer approach already used in transformStreamWithRouter for the same reason.

Why

Same hot path as the sibling zero-copy PR: decoding streamed server-function responses and RawStream payloads. The O(n²) bites specifically when one frame spans many buffered chunks.

Standalone micro-benchmark (Node, median of 12 runs), draining N buffered chunks:

Chunksshift()head pointerSpeedup
20026.7 ms2.5 ms10.6x
1000173 ms15 ms11.5x

Tests

  • Existing frame-decoder suite passes.
  • Added two tests for the changed paths:
    • a 200-byte CHUNK payload delivered one byte at a time (forces the header slow path + many whole-chunk consumptions + the fully-drained reset),
    • 100-byte frames fed in 7-byte reads that never align with frame boundaries, so the head pointer climbs past the compaction threshold repeatedly (exercises the splice() prefix drop).
nx run @tanstack/start-client-core:test:unit -> 20 passed
nx run @tanstack/start-client-core:test:types -> no errors

Notes

  • Independent of the sibling zero-copy PR. Both touch extractFlattened, so whichever merges second will need a trivial rebase.
  • Includes a changeset (@tanstack/start-client-core patch).

Summary by CodeRabbit

  • Performance

    • Optimized frame decoder to more efficiently handle large frames when data arrives as multiple small reads, reducing processing overhead.
  • Tests

    • Added test coverage for frame decoder with fragmented input streams and non-aligned chunk boundaries.

@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

createFrameDecoder in frame-decoder.ts now tracks the first unconsumed chunk via a bufferHead index instead of calling bufferList.shift(). Both paths of readHeader and the extractFlattened function are updated to use this pointer, with periodic list compaction. Two new tests cover one-byte-at-a-time and 7-byte-misaligned delivery, and a patch changeset entry is added.

Changes

Frame decoder O(1) buffer optimization

Layer / File(s)Summary
bufferHead pointer and extractFlattened rewrite
packages/start-client-core/src/client-rpc/frame-decoder.ts
Adds bufferHead to the parsing loop. Updates readHeader's fast path to read from bufferList[bufferHead] and its slow-path loop to iterate from bufferHead. Rewrites extractFlattened to advance bufferHead on full chunk consumption, replace partially consumed chunks with subarrays, and splice the consumed prefix after a compaction threshold.
Slow-path and misaligned-chunk tests, changeset
packages/start-client-core/tests/frame-decoder.test.ts, .changeset/perf-frame-decoder-index-pointer.md
Adds a test that feeds a CHUNK payload one byte per enqueue and reassembles it, adds a test that feeds multiple JSON frames in 7-byte misaligned reads and verifies ordered decoding, and records a patch changeset entry for the optimization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more shifting the pile — just a pointer so neat,
I hop to the head where the fresh chunks I meet.
O(n²) once loomed like a very tall stack,
Now O(1) each step, no slow tumble back.
The frames decode swiftly, the buffers stay trim —
A rabbit's best run, fast and light on a whim! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely captures the main performance optimization: replacing an O(n) shift operation with an O(1) head pointer approach in the client frame decoder.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/start-client-core/tests/frame-decoder.test.ts (1)

567-567: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use braces for one-line control statements in tests.

Line 567, Line 600-601, and Line 641 use single-line bodies without braces, which violates the project rule for TS/JS control flow.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

Suggested patch
- for (let i = 0; i < payload.length; i++) payload[i] = (i * 7) % 256+ for (let i = 0; i < payload.length; i++) {+ payload[i] = (i * 7) % 256+ }
while (true) {
const { done, value } = await rawReader.read()
- if (done) break- if (value) received.push(...value)+ if (done) {+ break+ }+ if (value) {+ received.push(...value)+ }
}
while (true) {
const { done, value } = await reader.read()
- if (done) break+ if (done) {+ break+ }
received.push(value)
}

Also applies to: 600-601, 641-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-client-core/tests/frame-decoder.test.ts` at line 567, The for
loop on line 567 with body payload[i] = (i * 7) % 256 uses a single-line body
without braces, violating the project's coding guidelines. Add curly braces
around the loop body to properly format it. Additionally, apply the same fix to
the other single-line control statements mentioned at lines 600-601 and 641 by
wrapping their bodies in curly braces.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/start-client-core/tests/frame-decoder.test.ts`:
- Line 567: The for loop on line 567 with body payload[i] = (i * 7) % 256 uses a
single-line body without braces, violating the project's coding guidelines. Add
curly braces around the loop body to properly format it. Additionally, apply the
same fix to the other single-line control statements mentioned at lines 600-601
and 641 by wrapping their bodies in curly braces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9195414-0588-45d1-b334-36aea734698b

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 38c9d48.

📒 Files selected for processing (3)
  • .changeset/perf-frame-decoder-index-pointer.md
  • packages/start-client-core/src/client-rpc/frame-decoder.ts
  • packages/start-client-core/tests/frame-decoder.test.ts

@nx-cloud

nx-cloudBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 38c9d48

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded14mView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 18sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-22 11:15:39 UTC

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7663

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7663

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7663

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7663

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7663

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7663

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7663

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7663

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7663

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7663

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7663

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7663

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7663

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7663

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7663

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7663

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7663

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7663

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7663

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7663

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7663

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7663

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7663

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7663

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7663

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7663

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7663

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7663

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7663

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7663

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7663

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7663

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7663

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7663

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7663

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7663

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7663

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7663

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7663

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7663

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7663

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7663

commit: 38c9d48

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️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
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem serialization-payload (solid)6.9 MB7.4 MB-5.96%
Memorymem aborted-requests (solid)1.8 MB1.6 MB+12.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing anonrig:perf/frame-decoder-index-pointer (38c9d48) with main (f23ed0f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (279a849) during the generation of this report, so f23ed0f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Sheraff

Copy link
Copy Markdown
Collaborator

Can you fix the conflicts on this one? And then I'll merge it

@Sheraff

Copy link
Copy Markdown
Collaborator

GitHub could not update this private-fork branch because of the conflict with #7662, so I resolved it on current main in replacement PR #8009. The original commit authorship is preserved there.

@anonriganonrig closed this Aug 16, 2026
@anonrig
anonrigforce-pushed the perf/frame-decoder-index-pointer branch from 38c9d48 to f97188fCompareAugust 16, 2026 16:39
Sign up for freeto 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

@anonrig@Sheraff
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

perf(start-client-core): O(1) buffer drain in client frame decoder - #7663

Closed
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer
Closed

perf(start-client-core): O(1) buffer drain in client frame decoder#7663
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer

Conversation

@anonrig

@anonriganonrig commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Replace the O(n) bufferList.shift() in the client-side frame decoder (packages/start-client-core/src/client-rpc/frame-decoder.ts) with an O(1) head pointer.

extractFlattened() dropped each fully-consumed chunk from the front of bufferList with shift(). When a single large frame (e.g. a big RawStream payload) is assembled from many small network reads, the extract loop calls shift() once per chunk — and each shift() re-indexes the whole array, so reassembly degrades to O(n²).

This PR tracks the first un-consumed chunk with a bufferHead index and advances it in O(1) instead of shifting. readHeader() reads from bufferHead as well. Consumed slots are released for GC, and the array is compacted:

  • fully drained (bufferHead === bufferList.length) → reset in O(1) (the common terminal state), or
  • once the consumed prefix grows past a small threshold → splice() it off (amortized O(1) per consumed chunk).

This mirrors the index-pointer approach already used in transformStreamWithRouter for the same reason.

Why

Same hot path as the sibling zero-copy PR: decoding streamed server-function responses and RawStream payloads. The O(n²) bites specifically when one frame spans many buffered chunks.

Standalone micro-benchmark (Node, median of 12 runs), draining N buffered chunks:

Chunksshift()head pointerSpeedup
20026.7 ms2.5 ms10.6x
1000173 ms15 ms11.5x

Tests

  • Existing frame-decoder suite passes.
  • Added two tests for the changed paths:
    • a 200-byte CHUNK payload delivered one byte at a time (forces the header slow path + many whole-chunk consumptions + the fully-drained reset),
    • 100-byte frames fed in 7-byte reads that never align with frame boundaries, so the head pointer climbs past the compaction threshold repeatedly (exercises the splice() prefix drop).
nx run @tanstack/start-client-core:test:unit -> 20 passed
nx run @tanstack/start-client-core:test:types -> no errors

Notes

  • Independent of the sibling zero-copy PR. Both touch extractFlattened, so whichever merges second will need a trivial rebase.
  • Includes a changeset (@tanstack/start-client-core patch).

Summary by CodeRabbit

  • Performance

    • Optimized frame decoder to more efficiently handle large frames when data arrives as multiple small reads, reducing processing overhead.
  • Tests

    • Added test coverage for frame decoder with fragmented input streams and non-aligned chunk boundaries.

@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

createFrameDecoder in frame-decoder.ts now tracks the first unconsumed chunk via a bufferHead index instead of calling bufferList.shift(). Both paths of readHeader and the extractFlattened function are updated to use this pointer, with periodic list compaction. Two new tests cover one-byte-at-a-time and 7-byte-misaligned delivery, and a patch changeset entry is added.

Changes

Frame decoder O(1) buffer optimization

Layer / File(s)Summary
bufferHead pointer and extractFlattened rewrite
packages/start-client-core/src/client-rpc/frame-decoder.ts
Adds bufferHead to the parsing loop. Updates readHeader's fast path to read from bufferList[bufferHead] and its slow-path loop to iterate from bufferHead. Rewrites extractFlattened to advance bufferHead on full chunk consumption, replace partially consumed chunks with subarrays, and splice the consumed prefix after a compaction threshold.
Slow-path and misaligned-chunk tests, changeset
packages/start-client-core/tests/frame-decoder.test.ts, .changeset/perf-frame-decoder-index-pointer.md
Adds a test that feeds a CHUNK payload one byte per enqueue and reassembles it, adds a test that feeds multiple JSON frames in 7-byte misaligned reads and verifies ordered decoding, and records a patch changeset entry for the optimization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more shifting the pile — just a pointer so neat,
I hop to the head where the fresh chunks I meet.
O(n²) once loomed like a very tall stack,
Now O(1) each step, no slow tumble back.
The frames decode swiftly, the buffers stay trim —
A rabbit's best run, fast and light on a whim! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely captures the main performance optimization: replacing an O(n) shift operation with an O(1) head pointer approach in the client frame decoder.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/start-client-core/tests/frame-decoder.test.ts (1)

567-567: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use braces for one-line control statements in tests.

Line 567, Line 600-601, and Line 641 use single-line bodies without braces, which violates the project rule for TS/JS control flow.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

Suggested patch
- for (let i = 0; i < payload.length; i++) payload[i] = (i * 7) % 256+ for (let i = 0; i < payload.length; i++) {+ payload[i] = (i * 7) % 256+ }
while (true) {
const { done, value } = await rawReader.read()
- if (done) break- if (value) received.push(...value)+ if (done) {+ break+ }+ if (value) {+ received.push(...value)+ }
}
while (true) {
const { done, value } = await reader.read()
- if (done) break+ if (done) {+ break+ }
received.push(value)
}

Also applies to: 600-601, 641-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-client-core/tests/frame-decoder.test.ts` at line 567, The for
loop on line 567 with body payload[i] = (i * 7) % 256 uses a single-line body
without braces, violating the project's coding guidelines. Add curly braces
around the loop body to properly format it. Additionally, apply the same fix to
the other single-line control statements mentioned at lines 600-601 and 641 by
wrapping their bodies in curly braces.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/start-client-core/tests/frame-decoder.test.ts`:
- Line 567: The for loop on line 567 with body payload[i] = (i * 7) % 256 uses a
single-line body without braces, violating the project's coding guidelines. Add
curly braces around the loop body to properly format it. Additionally, apply the
same fix to the other single-line control statements mentioned at lines 600-601
and 641 by wrapping their bodies in curly braces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9195414-0588-45d1-b334-36aea734698b

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 38c9d48.

📒 Files selected for processing (3)
  • .changeset/perf-frame-decoder-index-pointer.md
  • packages/start-client-core/src/client-rpc/frame-decoder.ts
  • packages/start-client-core/tests/frame-decoder.test.ts

@nx-cloud

nx-cloudBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 38c9d48

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded14mView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 18sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-22 11:15:39 UTC

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7663

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7663

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7663

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7663

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7663

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7663

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7663

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7663

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7663

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7663

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7663

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7663

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7663

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7663

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7663

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7663

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7663

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7663

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7663

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7663

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7663

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7663

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7663

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7663

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7663

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7663

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7663

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7663

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7663

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7663

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7663

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7663

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7663

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7663

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7663

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7663

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7663

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7663

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7663

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7663

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7663

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7663

commit: 38c9d48

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️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
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem serialization-payload (solid)6.9 MB7.4 MB-5.96%
Memorymem aborted-requests (solid)1.8 MB1.6 MB+12.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing anonrig:perf/frame-decoder-index-pointer (38c9d48) with main (f23ed0f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (279a849) during the generation of this report, so f23ed0f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Sheraff

Copy link
Copy Markdown
Collaborator

Can you fix the conflicts on this one? And then I'll merge it

@Sheraff

Copy link
Copy Markdown
Collaborator

GitHub could not update this private-fork branch because of the conflict with #7662, so I resolved it on current main in replacement PR #8009. The original commit authorship is preserved there.

@anonriganonrig closed this Aug 16, 2026
@anonrig
anonrigforce-pushed the perf/frame-decoder-index-pointer branch from 38c9d48 to f97188fCompareAugust 16, 2026 16:39
Sign up for freeto 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

@anonrig@Sheraff
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

perf(start-client-core): O(1) buffer drain in client frame decoder - #7663

Closed
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer
Closed

perf(start-client-core): O(1) buffer drain in client frame decoder#7663
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer

Conversation

@anonrig

@anonriganonrig commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Replace the O(n) bufferList.shift() in the client-side frame decoder (packages/start-client-core/src/client-rpc/frame-decoder.ts) with an O(1) head pointer.

extractFlattened() dropped each fully-consumed chunk from the front of bufferList with shift(). When a single large frame (e.g. a big RawStream payload) is assembled from many small network reads, the extract loop calls shift() once per chunk — and each shift() re-indexes the whole array, so reassembly degrades to O(n²).

This PR tracks the first un-consumed chunk with a bufferHead index and advances it in O(1) instead of shifting. readHeader() reads from bufferHead as well. Consumed slots are released for GC, and the array is compacted:

  • fully drained (bufferHead === bufferList.length) → reset in O(1) (the common terminal state), or
  • once the consumed prefix grows past a small threshold → splice() it off (amortized O(1) per consumed chunk).

This mirrors the index-pointer approach already used in transformStreamWithRouter for the same reason.

Why

Same hot path as the sibling zero-copy PR: decoding streamed server-function responses and RawStream payloads. The O(n²) bites specifically when one frame spans many buffered chunks.

Standalone micro-benchmark (Node, median of 12 runs), draining N buffered chunks:

Chunksshift()head pointerSpeedup
20026.7 ms2.5 ms10.6x
1000173 ms15 ms11.5x

Tests

  • Existing frame-decoder suite passes.
  • Added two tests for the changed paths:
    • a 200-byte CHUNK payload delivered one byte at a time (forces the header slow path + many whole-chunk consumptions + the fully-drained reset),
    • 100-byte frames fed in 7-byte reads that never align with frame boundaries, so the head pointer climbs past the compaction threshold repeatedly (exercises the splice() prefix drop).
nx run @tanstack/start-client-core:test:unit -> 20 passed
nx run @tanstack/start-client-core:test:types -> no errors

Notes

  • Independent of the sibling zero-copy PR. Both touch extractFlattened, so whichever merges second will need a trivial rebase.
  • Includes a changeset (@tanstack/start-client-core patch).

Summary by CodeRabbit

  • Performance

    • Optimized frame decoder to more efficiently handle large frames when data arrives as multiple small reads, reducing processing overhead.
  • Tests

    • Added test coverage for frame decoder with fragmented input streams and non-aligned chunk boundaries.

@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

createFrameDecoder in frame-decoder.ts now tracks the first unconsumed chunk via a bufferHead index instead of calling bufferList.shift(). Both paths of readHeader and the extractFlattened function are updated to use this pointer, with periodic list compaction. Two new tests cover one-byte-at-a-time and 7-byte-misaligned delivery, and a patch changeset entry is added.

Changes

Frame decoder O(1) buffer optimization

Layer / File(s)Summary
bufferHead pointer and extractFlattened rewrite
packages/start-client-core/src/client-rpc/frame-decoder.ts
Adds bufferHead to the parsing loop. Updates readHeader's fast path to read from bufferList[bufferHead] and its slow-path loop to iterate from bufferHead. Rewrites extractFlattened to advance bufferHead on full chunk consumption, replace partially consumed chunks with subarrays, and splice the consumed prefix after a compaction threshold.
Slow-path and misaligned-chunk tests, changeset
packages/start-client-core/tests/frame-decoder.test.ts, .changeset/perf-frame-decoder-index-pointer.md
Adds a test that feeds a CHUNK payload one byte per enqueue and reassembles it, adds a test that feeds multiple JSON frames in 7-byte misaligned reads and verifies ordered decoding, and records a patch changeset entry for the optimization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more shifting the pile — just a pointer so neat,
I hop to the head where the fresh chunks I meet.
O(n²) once loomed like a very tall stack,
Now O(1) each step, no slow tumble back.
The frames decode swiftly, the buffers stay trim —
A rabbit's best run, fast and light on a whim! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely captures the main performance optimization: replacing an O(n) shift operation with an O(1) head pointer approach in the client frame decoder.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/start-client-core/tests/frame-decoder.test.ts (1)

567-567: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use braces for one-line control statements in tests.

Line 567, Line 600-601, and Line 641 use single-line bodies without braces, which violates the project rule for TS/JS control flow.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

Suggested patch
- for (let i = 0; i < payload.length; i++) payload[i] = (i * 7) % 256+ for (let i = 0; i < payload.length; i++) {+ payload[i] = (i * 7) % 256+ }
while (true) {
const { done, value } = await rawReader.read()
- if (done) break- if (value) received.push(...value)+ if (done) {+ break+ }+ if (value) {+ received.push(...value)+ }
}
while (true) {
const { done, value } = await reader.read()
- if (done) break+ if (done) {+ break+ }
received.push(value)
}

Also applies to: 600-601, 641-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-client-core/tests/frame-decoder.test.ts` at line 567, The for
loop on line 567 with body payload[i] = (i * 7) % 256 uses a single-line body
without braces, violating the project's coding guidelines. Add curly braces
around the loop body to properly format it. Additionally, apply the same fix to
the other single-line control statements mentioned at lines 600-601 and 641 by
wrapping their bodies in curly braces.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/start-client-core/tests/frame-decoder.test.ts`:
- Line 567: The for loop on line 567 with body payload[i] = (i * 7) % 256 uses a
single-line body without braces, violating the project's coding guidelines. Add
curly braces around the loop body to properly format it. Additionally, apply the
same fix to the other single-line control statements mentioned at lines 600-601
and 641 by wrapping their bodies in curly braces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9195414-0588-45d1-b334-36aea734698b

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 38c9d48.

📒 Files selected for processing (3)
  • .changeset/perf-frame-decoder-index-pointer.md
  • packages/start-client-core/src/client-rpc/frame-decoder.ts
  • packages/start-client-core/tests/frame-decoder.test.ts

@nx-cloud

nx-cloudBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 38c9d48

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded14mView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 18sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-22 11:15:39 UTC

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7663

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7663

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7663

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7663

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7663

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7663

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7663

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7663

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7663

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7663

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7663

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7663

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7663

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7663

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7663

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7663

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7663

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7663

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7663

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7663

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7663

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7663

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7663

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7663

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7663

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7663

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7663

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7663

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7663

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7663

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7663

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7663

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7663

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7663

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7663

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7663

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7663

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7663

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7663

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7663

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7663

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7663

commit: 38c9d48

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️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
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem serialization-payload (solid)6.9 MB7.4 MB-5.96%
Memorymem aborted-requests (solid)1.8 MB1.6 MB+12.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing anonrig:perf/frame-decoder-index-pointer (38c9d48) with main (f23ed0f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (279a849) during the generation of this report, so f23ed0f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Sheraff

Copy link
Copy Markdown
Collaborator

Can you fix the conflicts on this one? And then I'll merge it

@Sheraff

Copy link
Copy Markdown
Collaborator

GitHub could not update this private-fork branch because of the conflict with #7662, so I resolved it on current main in replacement PR #8009. The original commit authorship is preserved there.

@anonriganonrig closed this Aug 16, 2026
@anonrig
anonrigforce-pushed the perf/frame-decoder-index-pointer branch from 38c9d48 to f97188fCompareAugust 16, 2026 16:39
Sign up for freeto 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

@anonrig@Sheraff
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

perf(start-client-core): O(1) buffer drain in client frame decoder - #7663

Closed
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer
Closed

perf(start-client-core): O(1) buffer drain in client frame decoder#7663
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer

Conversation

@anonrig

@anonriganonrig commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Replace the O(n) bufferList.shift() in the client-side frame decoder (packages/start-client-core/src/client-rpc/frame-decoder.ts) with an O(1) head pointer.

extractFlattened() dropped each fully-consumed chunk from the front of bufferList with shift(). When a single large frame (e.g. a big RawStream payload) is assembled from many small network reads, the extract loop calls shift() once per chunk — and each shift() re-indexes the whole array, so reassembly degrades to O(n²).

This PR tracks the first un-consumed chunk with a bufferHead index and advances it in O(1) instead of shifting. readHeader() reads from bufferHead as well. Consumed slots are released for GC, and the array is compacted:

  • fully drained (bufferHead === bufferList.length) → reset in O(1) (the common terminal state), or
  • once the consumed prefix grows past a small threshold → splice() it off (amortized O(1) per consumed chunk).

This mirrors the index-pointer approach already used in transformStreamWithRouter for the same reason.

Why

Same hot path as the sibling zero-copy PR: decoding streamed server-function responses and RawStream payloads. The O(n²) bites specifically when one frame spans many buffered chunks.

Standalone micro-benchmark (Node, median of 12 runs), draining N buffered chunks:

Chunksshift()head pointerSpeedup
20026.7 ms2.5 ms10.6x
1000173 ms15 ms11.5x

Tests

  • Existing frame-decoder suite passes.
  • Added two tests for the changed paths:
    • a 200-byte CHUNK payload delivered one byte at a time (forces the header slow path + many whole-chunk consumptions + the fully-drained reset),
    • 100-byte frames fed in 7-byte reads that never align with frame boundaries, so the head pointer climbs past the compaction threshold repeatedly (exercises the splice() prefix drop).
nx run @tanstack/start-client-core:test:unit -> 20 passed
nx run @tanstack/start-client-core:test:types -> no errors

Notes

  • Independent of the sibling zero-copy PR. Both touch extractFlattened, so whichever merges second will need a trivial rebase.
  • Includes a changeset (@tanstack/start-client-core patch).

Summary by CodeRabbit

  • Performance

    • Optimized frame decoder to more efficiently handle large frames when data arrives as multiple small reads, reducing processing overhead.
  • Tests

    • Added test coverage for frame decoder with fragmented input streams and non-aligned chunk boundaries.

@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

createFrameDecoder in frame-decoder.ts now tracks the first unconsumed chunk via a bufferHead index instead of calling bufferList.shift(). Both paths of readHeader and the extractFlattened function are updated to use this pointer, with periodic list compaction. Two new tests cover one-byte-at-a-time and 7-byte-misaligned delivery, and a patch changeset entry is added.

Changes

Frame decoder O(1) buffer optimization

Layer / File(s)Summary
bufferHead pointer and extractFlattened rewrite
packages/start-client-core/src/client-rpc/frame-decoder.ts
Adds bufferHead to the parsing loop. Updates readHeader's fast path to read from bufferList[bufferHead] and its slow-path loop to iterate from bufferHead. Rewrites extractFlattened to advance bufferHead on full chunk consumption, replace partially consumed chunks with subarrays, and splice the consumed prefix after a compaction threshold.
Slow-path and misaligned-chunk tests, changeset
packages/start-client-core/tests/frame-decoder.test.ts, .changeset/perf-frame-decoder-index-pointer.md
Adds a test that feeds a CHUNK payload one byte per enqueue and reassembles it, adds a test that feeds multiple JSON frames in 7-byte misaligned reads and verifies ordered decoding, and records a patch changeset entry for the optimization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more shifting the pile — just a pointer so neat,
I hop to the head where the fresh chunks I meet.
O(n²) once loomed like a very tall stack,
Now O(1) each step, no slow tumble back.
The frames decode swiftly, the buffers stay trim —
A rabbit's best run, fast and light on a whim! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely captures the main performance optimization: replacing an O(n) shift operation with an O(1) head pointer approach in the client frame decoder.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/start-client-core/tests/frame-decoder.test.ts (1)

567-567: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use braces for one-line control statements in tests.

Line 567, Line 600-601, and Line 641 use single-line bodies without braces, which violates the project rule for TS/JS control flow.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

Suggested patch
- for (let i = 0; i < payload.length; i++) payload[i] = (i * 7) % 256+ for (let i = 0; i < payload.length; i++) {+ payload[i] = (i * 7) % 256+ }
while (true) {
const { done, value } = await rawReader.read()
- if (done) break- if (value) received.push(...value)+ if (done) {+ break+ }+ if (value) {+ received.push(...value)+ }
}
while (true) {
const { done, value } = await reader.read()
- if (done) break+ if (done) {+ break+ }
received.push(value)
}

Also applies to: 600-601, 641-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-client-core/tests/frame-decoder.test.ts` at line 567, The for
loop on line 567 with body payload[i] = (i * 7) % 256 uses a single-line body
without braces, violating the project's coding guidelines. Add curly braces
around the loop body to properly format it. Additionally, apply the same fix to
the other single-line control statements mentioned at lines 600-601 and 641 by
wrapping their bodies in curly braces.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/start-client-core/tests/frame-decoder.test.ts`:
- Line 567: The for loop on line 567 with body payload[i] = (i * 7) % 256 uses a
single-line body without braces, violating the project's coding guidelines. Add
curly braces around the loop body to properly format it. Additionally, apply the
same fix to the other single-line control statements mentioned at lines 600-601
and 641 by wrapping their bodies in curly braces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9195414-0588-45d1-b334-36aea734698b

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 38c9d48.

📒 Files selected for processing (3)
  • .changeset/perf-frame-decoder-index-pointer.md
  • packages/start-client-core/src/client-rpc/frame-decoder.ts
  • packages/start-client-core/tests/frame-decoder.test.ts

@nx-cloud

nx-cloudBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 38c9d48

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded14mView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 18sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-22 11:15:39 UTC

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7663

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7663

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7663

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7663

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7663

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7663

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7663

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7663

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7663

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7663

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7663

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7663

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7663

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7663

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7663

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7663

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7663

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7663

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7663

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7663

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7663

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7663

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7663

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7663

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7663

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7663

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7663

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7663

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7663

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7663

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7663

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7663

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7663

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7663

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7663

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7663

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7663

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7663

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7663

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7663

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7663

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7663

commit: 38c9d48

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️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
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem serialization-payload (solid)6.9 MB7.4 MB-5.96%
Memorymem aborted-requests (solid)1.8 MB1.6 MB+12.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing anonrig:perf/frame-decoder-index-pointer (38c9d48) with main (f23ed0f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (279a849) during the generation of this report, so f23ed0f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Sheraff

Copy link
Copy Markdown
Collaborator

Can you fix the conflicts on this one? And then I'll merge it

@Sheraff

Copy link
Copy Markdown
Collaborator

GitHub could not update this private-fork branch because of the conflict with #7662, so I resolved it on current main in replacement PR #8009. The original commit authorship is preserved there.

@anonriganonrig closed this Aug 16, 2026
@anonrig
anonrigforce-pushed the perf/frame-decoder-index-pointer branch from 38c9d48 to f97188fCompareAugust 16, 2026 16:39
Sign up for freeto 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

@anonrig@Sheraff
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

perf(start-client-core): O(1) buffer drain in client frame decoder - #7663

Closed
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer
Closed

perf(start-client-core): O(1) buffer drain in client frame decoder#7663
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer

Conversation

@anonrig

@anonriganonrig commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Replace the O(n) bufferList.shift() in the client-side frame decoder (packages/start-client-core/src/client-rpc/frame-decoder.ts) with an O(1) head pointer.

extractFlattened() dropped each fully-consumed chunk from the front of bufferList with shift(). When a single large frame (e.g. a big RawStream payload) is assembled from many small network reads, the extract loop calls shift() once per chunk — and each shift() re-indexes the whole array, so reassembly degrades to O(n²).

This PR tracks the first un-consumed chunk with a bufferHead index and advances it in O(1) instead of shifting. readHeader() reads from bufferHead as well. Consumed slots are released for GC, and the array is compacted:

  • fully drained (bufferHead === bufferList.length) → reset in O(1) (the common terminal state), or
  • once the consumed prefix grows past a small threshold → splice() it off (amortized O(1) per consumed chunk).

This mirrors the index-pointer approach already used in transformStreamWithRouter for the same reason.

Why

Same hot path as the sibling zero-copy PR: decoding streamed server-function responses and RawStream payloads. The O(n²) bites specifically when one frame spans many buffered chunks.

Standalone micro-benchmark (Node, median of 12 runs), draining N buffered chunks:

Chunksshift()head pointerSpeedup
20026.7 ms2.5 ms10.6x
1000173 ms15 ms11.5x

Tests

  • Existing frame-decoder suite passes.
  • Added two tests for the changed paths:
    • a 200-byte CHUNK payload delivered one byte at a time (forces the header slow path + many whole-chunk consumptions + the fully-drained reset),
    • 100-byte frames fed in 7-byte reads that never align with frame boundaries, so the head pointer climbs past the compaction threshold repeatedly (exercises the splice() prefix drop).
nx run @tanstack/start-client-core:test:unit -> 20 passed
nx run @tanstack/start-client-core:test:types -> no errors

Notes

  • Independent of the sibling zero-copy PR. Both touch extractFlattened, so whichever merges second will need a trivial rebase.
  • Includes a changeset (@tanstack/start-client-core patch).

Summary by CodeRabbit

  • Performance

    • Optimized frame decoder to more efficiently handle large frames when data arrives as multiple small reads, reducing processing overhead.
  • Tests

    • Added test coverage for frame decoder with fragmented input streams and non-aligned chunk boundaries.

@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

createFrameDecoder in frame-decoder.ts now tracks the first unconsumed chunk via a bufferHead index instead of calling bufferList.shift(). Both paths of readHeader and the extractFlattened function are updated to use this pointer, with periodic list compaction. Two new tests cover one-byte-at-a-time and 7-byte-misaligned delivery, and a patch changeset entry is added.

Changes

Frame decoder O(1) buffer optimization

Layer / File(s)Summary
bufferHead pointer and extractFlattened rewrite
packages/start-client-core/src/client-rpc/frame-decoder.ts
Adds bufferHead to the parsing loop. Updates readHeader's fast path to read from bufferList[bufferHead] and its slow-path loop to iterate from bufferHead. Rewrites extractFlattened to advance bufferHead on full chunk consumption, replace partially consumed chunks with subarrays, and splice the consumed prefix after a compaction threshold.
Slow-path and misaligned-chunk tests, changeset
packages/start-client-core/tests/frame-decoder.test.ts, .changeset/perf-frame-decoder-index-pointer.md
Adds a test that feeds a CHUNK payload one byte per enqueue and reassembles it, adds a test that feeds multiple JSON frames in 7-byte misaligned reads and verifies ordered decoding, and records a patch changeset entry for the optimization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more shifting the pile — just a pointer so neat,
I hop to the head where the fresh chunks I meet.
O(n²) once loomed like a very tall stack,
Now O(1) each step, no slow tumble back.
The frames decode swiftly, the buffers stay trim —
A rabbit's best run, fast and light on a whim! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely captures the main performance optimization: replacing an O(n) shift operation with an O(1) head pointer approach in the client frame decoder.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/start-client-core/tests/frame-decoder.test.ts (1)

567-567: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use braces for one-line control statements in tests.

Line 567, Line 600-601, and Line 641 use single-line bodies without braces, which violates the project rule for TS/JS control flow.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

Suggested patch
- for (let i = 0; i < payload.length; i++) payload[i] = (i * 7) % 256+ for (let i = 0; i < payload.length; i++) {+ payload[i] = (i * 7) % 256+ }
while (true) {
const { done, value } = await rawReader.read()
- if (done) break- if (value) received.push(...value)+ if (done) {+ break+ }+ if (value) {+ received.push(...value)+ }
}
while (true) {
const { done, value } = await reader.read()
- if (done) break+ if (done) {+ break+ }
received.push(value)
}

Also applies to: 600-601, 641-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-client-core/tests/frame-decoder.test.ts` at line 567, The for
loop on line 567 with body payload[i] = (i * 7) % 256 uses a single-line body
without braces, violating the project's coding guidelines. Add curly braces
around the loop body to properly format it. Additionally, apply the same fix to
the other single-line control statements mentioned at lines 600-601 and 641 by
wrapping their bodies in curly braces.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/start-client-core/tests/frame-decoder.test.ts`:
- Line 567: The for loop on line 567 with body payload[i] = (i * 7) % 256 uses a
single-line body without braces, violating the project's coding guidelines. Add
curly braces around the loop body to properly format it. Additionally, apply the
same fix to the other single-line control statements mentioned at lines 600-601
and 641 by wrapping their bodies in curly braces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9195414-0588-45d1-b334-36aea734698b

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 38c9d48.

📒 Files selected for processing (3)
  • .changeset/perf-frame-decoder-index-pointer.md
  • packages/start-client-core/src/client-rpc/frame-decoder.ts
  • packages/start-client-core/tests/frame-decoder.test.ts

@nx-cloud

nx-cloudBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 38c9d48

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded14mView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 18sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-22 11:15:39 UTC

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7663

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7663

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7663

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7663

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7663

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7663

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7663

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7663

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7663

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7663

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7663

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7663

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7663

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7663

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7663

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7663

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7663

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7663

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7663

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7663

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7663

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7663

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7663

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7663

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7663

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7663

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7663

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7663

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7663

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7663

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7663

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7663

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7663

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7663

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7663

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7663

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7663

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7663

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7663

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7663

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7663

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7663

commit: 38c9d48

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️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
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem serialization-payload (solid)6.9 MB7.4 MB-5.96%
Memorymem aborted-requests (solid)1.8 MB1.6 MB+12.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing anonrig:perf/frame-decoder-index-pointer (38c9d48) with main (f23ed0f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (279a849) during the generation of this report, so f23ed0f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Sheraff

Copy link
Copy Markdown
Collaborator

Can you fix the conflicts on this one? And then I'll merge it

@Sheraff

Copy link
Copy Markdown
Collaborator

GitHub could not update this private-fork branch because of the conflict with #7662, so I resolved it on current main in replacement PR #8009. The original commit authorship is preserved there.

@anonriganonrig closed this Aug 16, 2026
@anonrig
anonrigforce-pushed the perf/frame-decoder-index-pointer branch from 38c9d48 to f97188fCompareAugust 16, 2026 16:39
Sign up for freeto 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

@anonrig@Sheraff
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

perf(start-client-core): O(1) buffer drain in client frame decoder - #7663

Closed
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer
Closed

perf(start-client-core): O(1) buffer drain in client frame decoder#7663
anonrig wants to merge 0 commit into
TanStack:mainfrom
anonrig:perf/frame-decoder-index-pointer

Conversation

@anonrig

@anonriganonrig commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What

Replace the O(n) bufferList.shift() in the client-side frame decoder (packages/start-client-core/src/client-rpc/frame-decoder.ts) with an O(1) head pointer.

extractFlattened() dropped each fully-consumed chunk from the front of bufferList with shift(). When a single large frame (e.g. a big RawStream payload) is assembled from many small network reads, the extract loop calls shift() once per chunk — and each shift() re-indexes the whole array, so reassembly degrades to O(n²).

This PR tracks the first un-consumed chunk with a bufferHead index and advances it in O(1) instead of shifting. readHeader() reads from bufferHead as well. Consumed slots are released for GC, and the array is compacted:

  • fully drained (bufferHead === bufferList.length) → reset in O(1) (the common terminal state), or
  • once the consumed prefix grows past a small threshold → splice() it off (amortized O(1) per consumed chunk).

This mirrors the index-pointer approach already used in transformStreamWithRouter for the same reason.

Why

Same hot path as the sibling zero-copy PR: decoding streamed server-function responses and RawStream payloads. The O(n²) bites specifically when one frame spans many buffered chunks.

Standalone micro-benchmark (Node, median of 12 runs), draining N buffered chunks:

Chunksshift()head pointerSpeedup
20026.7 ms2.5 ms10.6x
1000173 ms15 ms11.5x

Tests

  • Existing frame-decoder suite passes.
  • Added two tests for the changed paths:
    • a 200-byte CHUNK payload delivered one byte at a time (forces the header slow path + many whole-chunk consumptions + the fully-drained reset),
    • 100-byte frames fed in 7-byte reads that never align with frame boundaries, so the head pointer climbs past the compaction threshold repeatedly (exercises the splice() prefix drop).
nx run @tanstack/start-client-core:test:unit -> 20 passed
nx run @tanstack/start-client-core:test:types -> no errors

Notes

  • Independent of the sibling zero-copy PR. Both touch extractFlattened, so whichever merges second will need a trivial rebase.
  • Includes a changeset (@tanstack/start-client-core patch).

Summary by CodeRabbit

  • Performance

    • Optimized frame decoder to more efficiently handle large frames when data arrives as multiple small reads, reducing processing overhead.
  • Tests

    • Added test coverage for frame decoder with fragmented input streams and non-aligned chunk boundaries.

@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

createFrameDecoder in frame-decoder.ts now tracks the first unconsumed chunk via a bufferHead index instead of calling bufferList.shift(). Both paths of readHeader and the extractFlattened function are updated to use this pointer, with periodic list compaction. Two new tests cover one-byte-at-a-time and 7-byte-misaligned delivery, and a patch changeset entry is added.

Changes

Frame decoder O(1) buffer optimization

Layer / File(s)Summary
bufferHead pointer and extractFlattened rewrite
packages/start-client-core/src/client-rpc/frame-decoder.ts
Adds bufferHead to the parsing loop. Updates readHeader's fast path to read from bufferList[bufferHead] and its slow-path loop to iterate from bufferHead. Rewrites extractFlattened to advance bufferHead on full chunk consumption, replace partially consumed chunks with subarrays, and splice the consumed prefix after a compaction threshold.
Slow-path and misaligned-chunk tests, changeset
packages/start-client-core/tests/frame-decoder.test.ts, .changeset/perf-frame-decoder-index-pointer.md
Adds a test that feeds a CHUNK payload one byte per enqueue and reassembles it, adds a test that feeds multiple JSON frames in 7-byte misaligned reads and verifies ordered decoding, and records a patch changeset entry for the optimization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 No more shifting the pile — just a pointer so neat,
I hop to the head where the fresh chunks I meet.
O(n²) once loomed like a very tall stack,
Now O(1) each step, no slow tumble back.
The frames decode swiftly, the buffers stay trim —
A rabbit's best run, fast and light on a whim! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely captures the main performance optimization: replacing an O(n) shift operation with an O(1) head pointer approach in the client frame decoder.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/start-client-core/tests/frame-decoder.test.ts (1)

567-567: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use braces for one-line control statements in tests.

Line 567, Line 600-601, and Line 641 use single-line bodies without braces, which violates the project rule for TS/JS control flow.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

Suggested patch
- for (let i = 0; i < payload.length; i++) payload[i] = (i * 7) % 256+ for (let i = 0; i < payload.length; i++) {+ payload[i] = (i * 7) % 256+ }
while (true) {
const { done, value } = await rawReader.read()
- if (done) break- if (value) received.push(...value)+ if (done) {+ break+ }+ if (value) {+ received.push(...value)+ }
}
while (true) {
const { done, value } = await reader.read()
- if (done) break+ if (done) {+ break+ }
received.push(value)
}

Also applies to: 600-601, 641-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/start-client-core/tests/frame-decoder.test.ts` at line 567, The for
loop on line 567 with body payload[i] = (i * 7) % 256 uses a single-line body
without braces, violating the project's coding guidelines. Add curly braces
around the loop body to properly format it. Additionally, apply the same fix to
the other single-line control statements mentioned at lines 600-601 and 641 by
wrapping their bodies in curly braces.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/start-client-core/tests/frame-decoder.test.ts`:
- Line 567: The for loop on line 567 with body payload[i] = (i * 7) % 256 uses a
single-line body without braces, violating the project's coding guidelines. Add
curly braces around the loop body to properly format it. Additionally, apply the
same fix to the other single-line control statements mentioned at lines 600-601
and 641 by wrapping their bodies in curly braces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9195414-0588-45d1-b334-36aea734698b

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 38c9d48.

📒 Files selected for processing (3)
  • .changeset/perf-frame-decoder-index-pointer.md
  • packages/start-client-core/src/client-rpc/frame-decoder.ts
  • packages/start-client-core/tests/frame-decoder.test.ts

@nx-cloud

nx-cloudBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 38c9d48

CommandStatusDurationResult
nx affected --targets=test:eslint,test:unit,tes...✅ Succeeded14mView ↗
nx run-many --target=build --exclude=examples/*...✅ Succeeded2m 18sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-22 11:15:39 UTC

@pkg-pr-new

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7663

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7663

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7663

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7663

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7663

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7663

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7663

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7663

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7663

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7663

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7663

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7663

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7663

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7663

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7663

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7663

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7663

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7663

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7663

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7663

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7663

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7663

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7663

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7663

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7663

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7663

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7663

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7663

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7663

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7663

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7663

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7663

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7663

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7663

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7663

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7663

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7663

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7663

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7663

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7663

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7663

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7663

commit: 38c9d48

@codspeed-hq

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️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
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem serialization-payload (solid)6.9 MB7.4 MB-5.96%
Memorymem aborted-requests (solid)1.8 MB1.6 MB+12.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing anonrig:perf/frame-decoder-index-pointer (38c9d48) with main (f23ed0f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (279a849) during the generation of this report, so f23ed0f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Sheraff

Copy link
Copy Markdown
Collaborator

Can you fix the conflicts on this one? And then I'll merge it

@Sheraff

Copy link
Copy Markdown
Collaborator

GitHub could not update this private-fork branch because of the conflict with #7662, so I resolved it on current main in replacement PR #8009. The original commit authorship is preserved there.

@anonriganonrig closed this Aug 16, 2026
@anonrig
anonrigforce-pushed the perf/frame-decoder-index-pointer branch from 38c9d48 to f97188fCompareAugust 16, 2026 16:39
Sign up for freeto 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

@anonrig@Sheraff