From eef37f2cd79e45db93103baa4d80070373b72705 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 14 Jun 2026 23:48:19 +0200 Subject: [PATCH] bench(memory): add settle macro/micro-tasks after each loop to reduce peak memory allocation (hopefully) --- benchmarks/memory/client/lifecycle.ts | 7 +++++++ .../scenarios/interrupted-navigations/shared.ts | 4 ++++ .../client/scenarios/loader-data-retention/shared.ts | 4 ++++ .../memory/client/scenarios/mount-unmount/shared.ts | 2 ++ .../client/scenarios/navigation-churn/shared.ts | 4 ++++ .../memory/client/scenarios/preload-churn/shared.ts | 5 +++++ .../client/scenarios/unique-location-churn/shared.ts | 4 ++++ benchmarks/memory/server/bench-utils.ts | 11 +++++++++++ .../server/scenarios/aborted-requests/shared.ts | 4 ++-- 9 files changed, 43 insertions(+), 2 deletions(-) diff --git a/benchmarks/memory/client/lifecycle.ts b/benchmarks/memory/client/lifecycle.ts index c4a62f0f96..aaf9106a5b 100644 --- a/benchmarks/memory/client/lifecycle.ts +++ b/benchmarks/memory/client/lifecycle.ts @@ -44,3 +44,10 @@ export async function drainMicrotasks() { await Promise.resolve() await Promise.resolve() } + +// Render signals fire before framework/jsdom cleanup has fully settled; wait a +// microtask turn and one frame so churn iterations measure steady state. +export async function settleAfterRender() { + await drainMicrotasks() + await nextAnimationFrame() +} diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts index 0e01e92e08..d9685ec463 100644 --- a/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts +++ b/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts @@ -8,6 +8,7 @@ import { nextAnimationFrame, noop, removeBenchContainer, + settleAfterRender, warnClientMemoryDevMode, } from '#memory-client/lifecycle' import type { Framework, MountTestApp } from '#memory-client/lifecycle' @@ -164,6 +165,7 @@ export function createWorkload( for (let attempt = 0; attempt < 10; attempt++) { try { assertRenderedPage(page, id) + await settleAfterRender() return } catch { await nextAnimationFrame() @@ -171,6 +173,7 @@ export function createWorkload( } assertRenderedPage(page, id) + await settleAfterRender() } async function waitForSlowLoader(id: string) { @@ -229,6 +232,7 @@ export function createWorkload( }), rendered, ]) + await settleAfterRender() } startSlowNavigation = (id) => { diff --git a/benchmarks/memory/client/scenarios/loader-data-retention/shared.ts b/benchmarks/memory/client/scenarios/loader-data-retention/shared.ts index a5c496cb81..d34f441886 100644 --- a/benchmarks/memory/client/scenarios/loader-data-retention/shared.ts +++ b/benchmarks/memory/client/scenarios/loader-data-retention/shared.ts @@ -7,6 +7,7 @@ import { nextAnimationFrame, noop, removeBenchContainer, + settleAfterRender, warnClientMemoryDevMode, } from '#memory-client/lifecycle' import type { Framework, MountTestApp } from '#memory-client/lifecycle' @@ -90,6 +91,7 @@ export function createWorkload( for (let attempt = 0; attempt < 10; attempt++) { try { assertRenderedShell() + await settleAfterRender() return } catch { await nextAnimationFrame() @@ -97,6 +99,7 @@ export function createWorkload( } assertRenderedShell() + await settleAfterRender() } function waitForNextRender(pathname: string) { @@ -142,6 +145,7 @@ export function createWorkload( replace: true, }) await rendered + await settleAfterRender() assertRenderedPage(id) } diff --git a/benchmarks/memory/client/scenarios/mount-unmount/shared.ts b/benchmarks/memory/client/scenarios/mount-unmount/shared.ts index 4f9b9f430f..e9966ea0c3 100644 --- a/benchmarks/memory/client/scenarios/mount-unmount/shared.ts +++ b/benchmarks/memory/client/scenarios/mount-unmount/shared.ts @@ -3,6 +3,7 @@ import { drainMicrotasks, noop, removeBenchContainer, + settleAfterRender, warnClientMemoryDevMode, } from '#memory-client/lifecycle' import type { Framework, MountTestApp } from '#memory-client/lifecycle' @@ -47,6 +48,7 @@ export function createWorkload( await router.load() await rendered + await settleAfterRender() unsubscribe() unsubscribe = noop } finally { diff --git a/benchmarks/memory/client/scenarios/navigation-churn/shared.ts b/benchmarks/memory/client/scenarios/navigation-churn/shared.ts index 0745de2116..fca16843ae 100644 --- a/benchmarks/memory/client/scenarios/navigation-churn/shared.ts +++ b/benchmarks/memory/client/scenarios/navigation-churn/shared.ts @@ -3,6 +3,7 @@ import { nextAnimationFrame, noop, removeBenchContainer, + settleAfterRender, warnClientMemoryDevMode, } from '#memory-client/lifecycle' import type { Framework, MountTestApp } from '#memory-client/lifecycle' @@ -47,6 +48,7 @@ export function createWorkload( for (let attempt = 0; attempt < 10; attempt++) { try { assertRenderedPage(target) + await settleAfterRender() return } catch { await nextAnimationFrame() @@ -54,6 +56,7 @@ export function createWorkload( } assertRenderedPage(target) + await settleAfterRender() } function waitForNextRender() { @@ -86,6 +89,7 @@ export function createWorkload( }), rendered, ]) + await settleAfterRender() } await router.load() diff --git a/benchmarks/memory/client/scenarios/preload-churn/shared.ts b/benchmarks/memory/client/scenarios/preload-churn/shared.ts index 78e35c42ad..19b0fad6f6 100644 --- a/benchmarks/memory/client/scenarios/preload-churn/shared.ts +++ b/benchmarks/memory/client/scenarios/preload-churn/shared.ts @@ -8,6 +8,7 @@ import { nextAnimationFrame, noop, removeBenchContainer, + settleAfterRender, warnClientMemoryDevMode, } from '#memory-client/lifecycle' import type { Framework, MountTestApp } from '#memory-client/lifecycle' @@ -86,6 +87,7 @@ export function createWorkload( for (let attempt = 0; attempt < 10; attempt++) { try { assertRenderedIndex() + await settleAfterRender() return } catch { await nextAnimationFrame() @@ -93,6 +95,7 @@ export function createWorkload( } assertRenderedIndex() + await settleAfterRender() } function waitForNextRender() { @@ -134,6 +137,7 @@ export function createWorkload( }), rendered, ]) + await settleAfterRender() } navigateToIndex = async () => { @@ -145,6 +149,7 @@ export function createWorkload( }), rendered, ]) + await settleAfterRender() } await router.load() diff --git a/benchmarks/memory/client/scenarios/unique-location-churn/shared.ts b/benchmarks/memory/client/scenarios/unique-location-churn/shared.ts index 95b382269d..45942e4aca 100644 --- a/benchmarks/memory/client/scenarios/unique-location-churn/shared.ts +++ b/benchmarks/memory/client/scenarios/unique-location-churn/shared.ts @@ -7,6 +7,7 @@ import { nextAnimationFrame, noop, removeBenchContainer, + settleAfterRender, warnClientMemoryDevMode, } from '#memory-client/lifecycle' import type { Framework, MountTestApp } from '#memory-client/lifecycle' @@ -63,6 +64,7 @@ export function createWorkload( for (let attempt = 0; attempt < 10; attempt++) { try { assertRenderedId(expected) + await settleAfterRender() return } catch { await nextAnimationFrame() @@ -70,6 +72,7 @@ export function createWorkload( } assertRenderedId(expected) + await settleAfterRender() } function waitForNextRender() { @@ -104,6 +107,7 @@ export function createWorkload( }), rendered, ]) + await settleAfterRender() } await router.load() diff --git a/benchmarks/memory/server/bench-utils.ts b/benchmarks/memory/server/bench-utils.ts index f1f5dadea0..c222a70af1 100644 --- a/benchmarks/memory/server/bench-utils.ts +++ b/benchmarks/memory/server/bench-utils.ts @@ -60,6 +60,16 @@ export async function drainResponse(response: Response) { } } +// CodSpeed reports peak live memory, so queued stream/SSR cleanup from one +// response can otherwise be sampled during the next churn iteration. +export async function settleAfterDrainResponse() { + await Promise.resolve() + await Promise.resolve() + await new Promise((resolve) => { + setImmediate(resolve) + }) +} + export async function runSequentialRequestLoop( handler: StartRequestHandler, options: RunSequentialRequestLoopOptions, @@ -86,5 +96,6 @@ export async function runSequentialRequestLoop( validate(response, request) await drainResponse(response) + await settleAfterDrainResponse() } } diff --git a/benchmarks/memory/server/scenarios/aborted-requests/shared.ts b/benchmarks/memory/server/scenarios/aborted-requests/shared.ts index 9b71c37617..cfa9a00bb4 100644 --- a/benchmarks/memory/server/scenarios/aborted-requests/shared.ts +++ b/benchmarks/memory/server/scenarios/aborted-requests/shared.ts @@ -1,3 +1,4 @@ +import { settleAfterDrainResponse } from '#memory-server/bench-utils' import type { StartRequestHandler } from '#memory-server/bench-utils' export type { StartRequestHandler } @@ -189,8 +190,7 @@ async function cancelReader( } async function drainCancellation(mode: AbortedRequestDrainMode) { - await Promise.resolve() - await Promise.resolve() + await settleAfterDrainResponse() if (mode === 'tasks') { await new Promise((resolve) => setTimeout(resolve, 0))