Skip to content

bench(memory): settle-until-quiescent GC barrier between iterations - #7739

Merged
Sheraff merged 8 commits into
mainfrom
memory-bench-settle-barrier
Jul 3, 2026
Merged

bench(memory): settle-until-quiescent GC barrier between iterations#7739
Sheraff merged 8 commits into
mainfrom
memory-bench-settle-barrier

Conversation

@Sheraff

@SheraffSheraff commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Context

After #7730, memory benchmarks still flagged on PRs that never touched benchmarks/memory/** (observed throughout #7732). Analysis of 7 code-identical CI runs — including a same-commit main A/A pair — showed the survivors:

benchmarkpeak spread across identical codesignature
serialization-payload (solid)78% (3.6 vs 6.2/6.4 MB)bimodal: the pre-#7730 attractor resurfacing on some runners
serialization-payload (react)70% (3.17 vs 3.5/5.4 MB)same
aborted-requests (vue)21%continuous
request-churn (solid)18%peak floats while work is bit-identical (allocs ±0.9%) → pure GC scheduling

Root cause: the fixed 2-hop settle before the pinned collection is hardware-fragile. When renderer/stream teardown needs one more event-loop turn on a given runner (the GitHub pool mixes EPYC 9V74 / EPYC 7763 / Xeon 8370C), a whole payload of garbage slips past the collection point, V8 grows the heap, and the measured peak flips bimodally.

Changes

  • Settle-until-quiescent barrier (bench-utils.ts): hop one event-loop turn + gc() until the post-GC heap size stops moving (two identical consecutive readings), with a minimum-turn floor of 8 — React's stream teardown spans several turns and races a stability-only exit — and a cap of 16 so a drifting heap cannot stall the run. Smoke runs without --expose-gc keep the fixed timer hops. The barrier settles strictly more than the fixed-count one it replaces, never less.
  • Pin collections in churn scenarios too (request-churn, the aborted-requests drain): their peak floated on GC timing even with bit-identical work. Accumulation signals stay visible — a forced collection cannot reclaim leaked or cached objects, it only removes floating garbage, whose collection timing was the dominant noise source. Verified with a synthetic reachable leak (~1.6 MB over the run): +4.7% peak over the new 0.6% A/A noise floor, a clean flag where the old 18% noise would have swallowed it.

Validation (local, 3× codspeed run --mode memory per suite, Node 24)

  • solid: all 10 benches ≤1.0% A/A spread (request-churn 18%→0.8%, serialization-payload 78%→0.6%)
  • vue: ≤2.3% except aborted-requests ~8% (down from 21%; residual is intra-iteration work variance in the shell-read path — follow-up)
  • react: ≤2.2% except a rare +130 KB mode in request-churn (framework teardown variance; was bit-stable on CI before, watching the CI A/A here)
  • typecheck, prettier, and no-instrumentation smoke runs pass

Expected on this PR

One-time baseline shifts on the pinned benches (e.g. request-churn (solid)1.2 MB→470 KB, aborted-requests levels move, totalAllocated rises from forced compactions). Two workflow_dispatch A/A runs are already queued on this branch to measure real CI spreads.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Improved settling and forced-GC handling between benchmark iterations to reduce cross-run measurement noise.
    • Enabled GC pinning between iterations across multiple memory benchmark scenarios.
    • Stabilized benchmark memory/peak results by preventing mid-run bytecode flushing and applying V8 heap/space sizing.
  • Refactor
    • Consolidated the iteration “settlement barrier” into a shared flow.
  • Documentation
    • Clarified how forced-GC boundaries better reflect a single-iteration footprint and improve peak stability.
  • Chores
    • Reduced iteration counts in several workloads to make runs faster and more consistent.

The fixed-turn settle before the pinned collection was hardware-fragile:
teardown needing one more event-loop turn on a given runner leaked a whole
payload of garbage past the collection point and flipped the measured peak
bimodally (serialization-payload solid/react resurfacing the pre-#7730
6.2MB attractor on some CI runners). Replace it with an adaptive barrier
that hops and collects until the post-GC heap size stops moving, with a
minimum-turn floor matching the fixed count it replaces (React's stream
teardown spans several turns and races a shorter floor).
Also pin collections in request-churn and the aborted-requests drain:
their peak floated on GC scheduling even with bit-identical work (18%
spread on CI for solid churn). Reachable accumulation stays visible to
the metric - a forced collection cannot reclaim leaked or cached objects,
it only removes floating garbage, whose collection timing was the
dominant cross-run noise source. Verified with a synthetic reachable
leak: +4.7% peak over a 0.6% A/A noise floor, where the old 18% noise
would have swallowed it.
Local A/A spreads (3x codspeed run per suite): solid <=1%, react <=2.2%
except request-churn (rare +130KB mode, framework teardown variance),
vue <=2.3% except aborted-requests (~8%, intra-iteration work variance,
down from 21% on CI).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors memory benchmark GC settling into a shared async helper, enables GC pinning in several sequential workloads, reduces iteration counts in multiple benchmark scenarios, and adds runtime flags to benchmark Vitest configs.

Changes

GC pinning and benchmark tuning

Layer / File(s)Summary
Helper implementation
benchmarks/memory/server/bench-utils.ts
Updates the GC pinning documentation, changes runSequentialRequestLoop to call a new async helper, and adds the helper that performs timer hops around optional GC calls.
Scenario wiring
benchmarks/memory/server/scenarios/aborted-requests/shared.ts, benchmarks/memory/server/scenarios/request-churn/shared.ts, benchmarks/memory/server/scenarios/error-paths/shared.ts, benchmarks/memory/server/scenarios/server-fn-churn/shared.ts
Aborted requests use the new settlement helper, and several sequential benchmark loops enable pinGcBetweenIterations.
Iteration count reductions
benchmarks/memory/server/scenarios/aborted-requests/shared.ts, benchmarks/memory/server/scenarios/error-paths/shared.ts, benchmarks/memory/server/scenarios/request-churn/shared.ts, benchmarks/memory/server/scenarios/serialization-payload/shared.ts, benchmarks/memory/server/scenarios/server-fn-churn/shared.ts
Multiple benchmark workloads lower their configured iteration counts.
Benchmark runtime flags
benchmarks/memory/server/scenarios/aborted-requests/*/vite.config.ts, benchmarks/memory/server/scenarios/error-paths/*/vite.config.ts, benchmarks/memory/server/scenarios/peak-large-page/*/vite.config.ts, benchmarks/memory/server/scenarios/request-churn/*/vite.config.ts, benchmarks/memory/server/scenarios/serialization-payload/*/vite.config.ts, benchmarks/memory/server/scenarios/server-fn-churn/*/vite.config.ts, benchmarks/memory/server/scenarios/streaming-peak/*/vite.config.ts
Vitest configs add test.execArgv with --no-flush-bytecode and V8 heap sizing flags for the benchmark run.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • TanStack/router#7623: Both PRs touch benchmarks/memory/server/bench-utils.ts and the sequential benchmark loop behavior.
  • TanStack/router#7730: Both PRs change the aborted-requests settlement barrier logic in shared.ts.

Suggested reviewers:schiller-manuel

🚥 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 matches the main change: adding a settle-until-quiescent GC barrier between benchmark iterations.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch memory-bench-settle-barrier

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

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

No changeset entries found. Merging this PR will not cause a version bump for any packages.

@github-actions

github-actionsBot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: 253520dd5843
  • Measured at: 2026-07-03T19:12:59.885Z
  • Baseline source: history:208100b7c0a8
  • Dashboard: bundle-size history
ScenarioCurrent (gzip)Delta vs baselineInitial gzipRawBrotliTrend
react-router.minimal87.37 KiB0 B (0.00%)87.24 KiB273.88 KiB75.99 KiB▁▁▁▄▄▄▄████
react-router.full91.10 KiB0 B (0.00%)90.96 KiB285.78 KiB79.15 KiB▁▁▁▅▅▅▅████
solid-router.minimal35.56 KiB0 B (0.00%)35.44 KiB106.08 KiB32.05 KiB▁▁▁▅▅▅▅████
solid-router.full40.61 KiB0 B (0.00%)40.49 KiB121.30 KiB36.54 KiB▁▁▁▆▆▆▆████
vue-router.minimal53.07 KiB0 B (0.00%)52.94 KiB150.12 KiB47.70 KiB▁▁▁▄▄▄▄████
vue-router.full59.03 KiB0 B (0.00%)58.90 KiB168.88 KiB52.93 KiB▁▁▁▄▄▄▄████
react-start.minimal102.03 KiB0 B (0.00%)101.89 KiB322.31 KiB88.32 KiB▁▁▁▃▃▃▆████
react-start.deferred-hydration102.76 KiB0 B (0.00%)101.91 KiB323.69 KiB88.91 KiB▁▁▁▄▄▄▆████
react-start.full105.45 KiB0 B (0.00%)105.31 KiB332.24 KiB91.23 KiB▁▁▁▃▃▃▅████
react-start.rsbuild.minimal99.74 KiB0 B (0.00%)99.57 KiB316.74 KiB85.81 KiB▁▁▁▃▂▂▅████
react-start.rsbuild.minimal-iife100.14 KiB0 B (0.00%)99.98 KiB317.67 KiB86.21 KiB▁▁▁▃▂▂▅████
react-start.rsbuild.full102.96 KiB0 B (0.00%)102.79 KiB326.79 KiB88.60 KiB▁▁▁▃▃▃▅████
solid-start.minimal49.71 KiB0 B (0.00%)49.58 KiB152.25 KiB43.88 KiB▁▁▁▃▃▃▆████
solid-start.deferred-hydration52.97 KiB0 B (0.00%)49.64 KiB160.29 KiB46.85 KiB▁▁▁▃▃▃▅████
solid-start.full55.51 KiB0 B (0.00%)55.38 KiB169.30 KiB48.97 KiB▁▁▁▄▄▄▆████
vue-start.minimal71.13 KiB0 B (0.00%)71.00 KiB207.37 KiB63.03 KiB▁▁▁▄▄▄▇████
vue-start.full75.13 KiB0 B (0.00%)75.00 KiB220.01 KiB66.37 KiB▁▁▁▄▄▄▆████

Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better.

@nx-cloud

nx-cloudBot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 1517be5

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

☁️ Nx Cloud last updated this comment at 2026-07-03 19:14:49 UTC

@pkg-pr-new

pkg-pr-newBot commented Jul 3, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-rsc

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 1517be5

@codspeed-hq

codspeed-hqBot commented Jul 3, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 39.28%

⚠️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

⚡ 17 improved benchmarks
❌ 9 (👁 9) regressed benchmarks
✅ 154 untouched benchmarks

Performance Changes

ModeBenchmarkBASEHEADEfficiency
Memorymem error-paths unmatched (react)1,117.5 KB300.3 KB×3.7
Memorymem error-paths not-found (react)942.4 KB266.7 KB×3.5
Memorymem request-churn (react)1,622 KB497.5 KB×3.3
Memorymem request-churn (solid)1,134.2 KB417.3 KB×2.7
Memorymem aborted-requests (react)1,324.5 KB525.7 KB×2.5
Memorymem error-paths redirect (react)470 KB211.4 KB×2.2
Memorymem error-paths not-found (vue)758.7 KB381.3 KB+99%
Memorymem aborted-requests (vue)911.7 KB458.9 KB+98.66%
Memorymem aborted-requests (solid)1,846.7 KB936.8 KB+97.13%
Memorymem error-paths not-found (solid)737.1 KB410.3 KB+79.62%
Memorymem request-churn (vue)1,280 KB782.7 KB+63.53%
Memorymem server-fn-churn (solid)394.6 KB268.5 KB+46.96%
Memorymem server-fn-churn (vue)394.6 KB274.7 KB+43.68%
Memorymem server-fn-churn (react)394 KB276.9 KB+42.31%
Memorymem error-paths unmatched (vue)563 KB467.5 KB+20.41%
Memorymem unique-location-churn (solid)998.4 KB842.6 KB+18.49%
Memorymem error-paths redirect (solid)469.9 KB441.2 KB+6.52%
👁Memorymem error-paths error (solid)724.4 KB912.9 KB-20.65%
👁Memorymem error-paths unmatched (solid)256.3 KB472.6 KB-45.76%
👁Memorymem peak-large-page (solid)760.4 KB802.9 KB-5.29%
..................

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing memory-bench-settle-barrier (1517be5) with main (208100b)

Open in CodSpeed

Sheraffand others added 3 commits July 3, 2026 16:34
The adaptive exit (collect until post-GC heap size stops moving) made the
collection points land at data-dependent turns on CI: heap readings never
fully stabilize there, so the exit point - and every subsequent GC point -
shifted between identical runs. The first CI A/A pair showed it flipping
serialization-payload (vue) to a 6MB mode (67%), inflating peak-large-page
(vue) 3.4x, and adding 7-8% swings to the react/vue abort scenarios, while
fixing the solid suite.
Every step is now a fixed count: 16 settle turns (enough for React/Vue
multi-turn teardown that the original 2-turn barrier raced), one pinned
collection, one turn, one more collection for finalizer fallout. Fixed
counts make the GC points identical across runs by construction, which is
the property the barrier exists to provide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On CI (eBPF instrument + --no-opt) one pinned iteration costs ~0.1-0.6s,
so the previous counts put request-churn at ~36s measured and the error
-paths/server-fn benches at 0.1-0.5s - outside the useful 2-10s range in
both directions. Retune: request-churn/aborted-requests/error-paths/
server-fn-churn to 40 iterations, serialization-payload to 12;
peak-large-page and streaming-peak already sit in range at 20.
The floor is raised by pinning error-paths and server-fn-churn rather
than inflating their iteration counts: thousands of unpinned iterations
would cross V8's own GC scheduling and reintroduce the timing noise the
barrier removes, while the pin's fixed per-iteration cost lifts them into
range at 40 iterations and makes their collection points deterministic.
Note: fewer churn iterations proportionally shrink the accumulation
window, so peak-based leak sensitivity drops accordingly; leaks remain
visible in total-allocated (forced compactions re-copy the growing live
set every iteration).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The retuned (shorter) runs exposed a recurring V8 internal event - a
+7-13MB allocation burst with the signature of bytecode flush + lazy
recompile - that lands inside or outside the measured window at random,
flipping peaks by whole MBs between identical CI runs (request-churn vue
785KB vs 3.2MB). The pinned collections age code fast enough for V8 to
flush unused bytecode mid-run; --no-flush-bytecode keeps compiled code
alive for the process lifetime so the measured window sees no recompile
bursts. Vite's mergeConfig concatenates the scenario's test.execArgv with
the flags the CodSpeed plugin injects, so both apply to the fork workers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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)
benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts (1)

25-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Comment references pinned collections that don't apply to this scenario.

The rationale comment says "the pinned collections age code fast enough for V8 to flush unused bytecode," but per the scenario-wiring changes, GC pinning is only added to aborted-requests, request-churn, error-paths, and server-fn-churn — not peak-large-page. This looks like a copy-pasted comment that misattributes the cause here.

✏️ Suggested comment fix
- // Keep lazily-compiled code alive for the whole run: the pinned- // collections age code fast enough for V8 to flush unused bytecode,- // and the mid-measurement recompile injects a multi-MB allocation- // burst at a run-dependent time.+ // Keep lazily-compiled code alive for the whole run: V8 may flush+ // unused bytecode mid-run, and the recompile injects a multi-MB+ // allocation burst at a run-dependent time.
execArgv: ['--no-flush-bytecode'],
🤖 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 `@benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts`
around lines 25 - 29, The rationale comment on the execArgv setting is
misattributing GC pinning to this scenario, since peak-large-page does not use
the pinned collections added in the other scenarios. Update the comment near the
execArgv ['--no-flush-bytecode'] configuration in vite.config.ts to describe
only the actual behavior for peak-large-page, keeping the note about preserving
lazily compiled code alive without referring to pinned collections or the other
scenario-specific GC wiring.
🤖 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 `@benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts`:
- Around line 25-29: The rationale comment on the execArgv setting is
misattributing GC pinning to this scenario, since peak-large-page does not use
the pinned collections added in the other scenarios. Update the comment near the
execArgv ['--no-flush-bytecode'] configuration in vite.config.ts to describe
only the actual behavior for peak-large-page, keeping the note about preserving
lazily compiled code alive without referring to pinned collections or the other
scenario-specific GC wiring.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 566d0502-4751-4171-add6-f7b6c745f781

📥 Commits

Reviewing files that changed from the base of the PR and between cb1dc43 and 9303131.

📒 Files selected for processing (21)
  • benchmarks/memory/server/scenarios/aborted-requests/react/vite.config.ts
  • benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/aborted-requests/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/error-paths/react/vite.config.ts
  • benchmarks/memory/server/scenarios/error-paths/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/error-paths/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts
  • benchmarks/memory/server/scenarios/peak-large-page/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/peak-large-page/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/request-churn/react/vite.config.ts
  • benchmarks/memory/server/scenarios/request-churn/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/request-churn/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/serialization-payload/react/vite.config.ts
  • benchmarks/memory/server/scenarios/serialization-payload/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/serialization-payload/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/server-fn-churn/react/vite.config.ts
  • benchmarks/memory/server/scenarios/server-fn-churn/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/server-fn-churn/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/streaming-peak/react/vite.config.ts
  • benchmarks/memory/server/scenarios/streaming-peak/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/streaming-peak/vue/vite.config.ts

--no-flush-bytecode alone did not stop the run-to-run flips: the
recurring event is heap-space growth, which allocates several MB at a
run-dependent moment inside the shortened measured windows (error-paths
unmatched vue 674KB vs 2MB, serialization-payload solid resurfacing its
6.4MB attractor on one run in three). Pre-sizing old space and pinning
the semi-space size removes mid-measurement growth while leaving the
per-iteration page map/unmap signal intact - local runs confirm peaks
still track payload sizes exactly (serialization 3.5MB, streaming
30.3MB, levels unchanged).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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 (2)
benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared execArgv block to avoid drift across ~20 configs.

This identical array (flags + comments) is duplicated verbatim across at least 20 scenario vite.config.ts files in this PR. Any future tuning of the heap-sizing values will require touching every file consistently, risking drift.

Consider extracting to a shared constant in benchmarks/memory/server/bench-utils.ts (or a dedicated config module) and importing it into each scenario config.

♻️ Example extraction
// benchmarks/memory/server/bench-execargv.tsexportconststableHeapExecArgv=['--no-flush-bytecode','--initial-old-space-size=64','--min-semi-space-size=16','--max-semi-space-size=16',];
- execArgv: [- '--no-flush-bytecode',- '--initial-old-space-size=64',- '--min-semi-space-size=16',- '--max-semi-space-size=16',- ],+ execArgv: stableHeapExecArgv,
🤖 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 `@benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts`
around lines 29 - 37, The execArgv array is duplicated across many scenario
vite.config.ts files, which will cause config drift when the heap flags change.
Extract the shared flags into a single exported constant in a common module such
as bench-utils.ts or a dedicated bench-execargv.ts, then import and reuse it
from this Vite config and the other scenario configs that currently inline the
same block.
benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated execArgv block across ~20 configs — extract to a shared constant.

The same 4-flag array (plus comments) is repeated verbatim in every scenario's vite.config.ts (react/solid/vue × 7 scenarios). Centralizing this in a shared module (e.g., benchmarks/memory/server/bench-execargv.ts) would avoid drift if flag values are re-tuned later, and matches the PR's own instinct to keep GC-settling logic in a shared helper.

♻️ Suggested extraction
// benchmarks/memory/server/bench-execargv.tsexportconstmemoryBenchExecArgv=['--no-flush-bytecode','--initial-old-space-size=64','--min-semi-space-size=16','--max-semi-space-size=16',]
+import { memoryBenchExecArgv } from '../../../bench-execargv'
...
- execArgv: [- '--no-flush-bytecode',- // Pre-size the V8 heap so no space has to grow mid-measurement:- // heap-growth events allocate several MB at a run-dependent moment,- // which flips the measured peak bimodally between identical runs.- '--initial-old-space-size=64',- '--min-semi-space-size=16',- '--max-semi-space-size=16',- ],+ execArgv: memoryBenchExecArgv,
🤖 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 `@benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts`
around lines 29 - 37, The repeated execArgv flag array in the scenario Vite
configs should be centralized into a shared constant to prevent drift across the
many duplicated files. Extract the common 4-flag setup from the
scenario-specific vite.config.ts files into a shared module such as
bench-execargv.ts, then import and reuse it from each config; keep the unique
scenario wiring in place while referencing the shared memoryBenchExecArgv
symbol.
🤖 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 `@benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts`:
- Around line 29-37: The execArgv array is duplicated across many scenario
vite.config.ts files, which will cause config drift when the heap flags change.
Extract the shared flags into a single exported constant in a common module such
as bench-utils.ts or a dedicated bench-execargv.ts, then import and reuse it
from this Vite config and the other scenario configs that currently inline the
same block.
In `@benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts`:
- Around line 29-37: The repeated execArgv flag array in the scenario Vite
configs should be centralized into a shared constant to prevent drift across the
many duplicated files. Extract the common 4-flag setup from the
scenario-specific vite.config.ts files into a shared module such as
bench-execargv.ts, then import and reuse it from each config; keep the unique
scenario wiring in place while referencing the shared memoryBenchExecArgv
symbol.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c668745-5828-4db9-a678-c30c2dffb80e

📥 Commits

Reviewing files that changed from the base of the PR and between 9303131 and 4e5deed.

📒 Files selected for processing (21)
  • benchmarks/memory/server/scenarios/aborted-requests/react/vite.config.ts
  • benchmarks/memory/server/scenarios/aborted-requests/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/aborted-requests/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/error-paths/react/vite.config.ts
  • benchmarks/memory/server/scenarios/error-paths/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/error-paths/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/peak-large-page/react/vite.config.ts
  • benchmarks/memory/server/scenarios/peak-large-page/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/peak-large-page/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/request-churn/react/vite.config.ts
  • benchmarks/memory/server/scenarios/request-churn/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/request-churn/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/serialization-payload/react/vite.config.ts
  • benchmarks/memory/server/scenarios/serialization-payload/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/serialization-payload/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/server-fn-churn/react/vite.config.ts
  • benchmarks/memory/server/scenarios/server-fn-churn/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/server-fn-churn/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/streaming-peak/react/vite.config.ts
  • benchmarks/memory/server/scenarios/streaming-peak/solid/vite.config.ts
  • benchmarks/memory/server/scenarios/streaming-peak/vue/vite.config.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • benchmarks/memory/server/scenarios/aborted-requests/react/vite.config.ts
  • benchmarks/memory/server/scenarios/error-paths/react/vite.config.ts
  • benchmarks/memory/server/scenarios/serialization-payload/react/vite.config.ts
  • benchmarks/memory/server/scenarios/streaming-peak/vue/vite.config.ts
  • benchmarks/memory/server/scenarios/request-churn/react/vite.config.ts

Sheraffand others added 3 commits July 3, 2026 19:23
error-paths and server-fn-churn sat at 5-8s measured on CI; fewer
iterations bring them to ~2.3-3.4s, keeping the 2s floor while shrinking
the window a stray allocator event can land in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The residual CI flips all share one signature: exactly one iteration's
payload added to the measured peak (serialization-payload vue 3.5 vs
6.9MB, peak-large-page vue 780 vs 1349KB, error-paths redirect vue 300 vs
885KB). On some runners the response teardown holds the payload past the
fixed settle window - released only by a later internal timer - so it
survives the pinned collection and bleeds into the next iteration.
After the fixed 16-turn settle and collection, compare the post-GC heap
against the smallest post-collection size seen this run (the inter-
iteration floor): while it has not returned to the floor, keep hopping
and collecting, bounded at 64 turns. The barrier stays fixed-count in the
common case and extends only in exactly the failure case. Workloads that
genuinely accumulate reachable memory raise the floor as they go, so
accumulation still measures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The floor-verified barrier fixed its target - the serialization family
went to <=0.45% across three CI runs, attractor gone - but destabilized
churn/abort scenarios (request-churn solid 420KB vs 3.4MB): their inter-
iteration floor legitimately drifts, so the verification capped out
chronically and the ~48 extra collections per iteration eventually hit a
compaction that double-copies the live set. Split by scenario shape:
serialization-payload, peak-large-page and streaming-peak keep the
verified barrier (flat floor, proven fix); churn and abort scenarios go
back to the plain fixed-count barrier that held them at <=1.7% in the
two previous CI rounds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Sheraff
Sheraff merged commit d644dbb into mainJul 3, 2026
50 checks passed
@Sheraff
Sheraff deleted the memory-bench-settle-barrier branch July 3, 2026 19:43
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.

1 participant

@Sheraff