From ebb97a618cb61af95323e874d515c36613c9dc69 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:51:43 +0100 Subject: [PATCH] perf: don't use tracing overeager during dev (#17183) * perf: don't use tracing overeager during dev #17176 is a case where many sources are created and then written to (due to Svelte 4 prop mechanics), and our tracing kicked in eagerly. That combined with the excessive depth of the related stack traces slowed things down tremendously. The fix is simple: Don't record stack traces until we've seen this source get updated for a couple of times. Additionally we now delete the `updates` map after a flush. Previously it was just an ever-growing stack trace map. * fix * fix --- .changeset/salty-hounds-worry.md | 5 ++++ .../svelte/src/compiler/phases/types.d.ts | 1 + .../svelte/src/internal/client/dev/tracing.js | 6 +++-- .../src/internal/client/reactivity/batch.js | 20 ++++++++++++-- .../src/internal/client/reactivity/sources.js | 26 ++++++++++++------- packages/svelte/src/internal/flags/index.js | 3 +++ 6 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 .changeset/salty-hounds-worry.md diff --git a/.changeset/salty-hounds-worry.md b/.changeset/salty-hounds-worry.md new file mode 100644 index 0000000000..82a887450c --- /dev/null +++ b/.changeset/salty-hounds-worry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +perf: don't use tracing overeager during dev diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 71f77b1a11..5397ea45f9 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -53,6 +53,7 @@ export interface Analysis { /** @deprecated use `runes` from `state.js` instead */ runes: boolean; immutable: boolean; + /** True if `$inspect.trace` is used */ tracing: boolean; comments: AST.JSComment[]; diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 4688637f5d..183da73447 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -57,8 +57,10 @@ function log_entry(signal, entry) { if (dirty && signal.updated) { for (const updated of signal.updated.values()) { - // eslint-disable-next-line no-console - console.log(updated.error); + if (updated.error) { + // eslint-disable-next-line no-console + console.log(updated.error); + } } } diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c7e01fbcba..22526df7c1 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -608,6 +608,8 @@ function flush_effects() { var was_updating_effect = is_updating_effect; is_flushing = true; + var source_stacks = DEV ? new Set() : null; + try { var flush_count = 0; set_is_updating_effect(true); @@ -633,8 +635,10 @@ function flush_effects() { } for (const update of updates.values()) { - // eslint-disable-next-line no-console - console.error(update.error); + if (update.error) { + // eslint-disable-next-line no-console + console.error(update.error); + } } } @@ -643,12 +647,24 @@ function flush_effects() { batch.process(queued_root_effects); old_values.clear(); + + if (DEV) { + for (const source of batch.current.keys()) { + /** @type {Set} */ (source_stacks).add(source); + } + } } } finally { is_flushing = false; set_is_updating_effect(was_updating_effect); last_scheduled_effect = null; + + if (DEV) { + for (const source of /** @type {Set} */ (source_stacks)) { + source.updated = null; + } + } } } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 052ca97dc0..822fb21822 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -188,18 +188,26 @@ export function internal_set(source, value) { if (DEV) { if (tracing_mode_flag || active_effect !== null) { - const error = get_stack('updated at'); + source.updated ??= new Map(); - if (error !== null) { - source.updated ??= new Map(); - let entry = source.updated.get(error.stack); + // For performance reasons, when not using $inspect.trace, we only start collecting stack traces + // after the same source has been updated more than 5 times in the same flush cycle. + const count = (source.updated.get('')?.count ?? 0) + 1; + source.updated.set('', { error: /** @type {any} */ (null), count }); - if (!entry) { - entry = { error, count: 0 }; - source.updated.set(error.stack, entry); - } + if (tracing_mode_flag || count > 5) { + const error = get_stack('updated at'); + + if (error !== null) { + let entry = source.updated.get(error.stack); - entry.count++; + if (!entry) { + entry = { error, count: 0 }; + source.updated.set(error.stack, entry); + } + + entry.count++; + } } } diff --git a/packages/svelte/src/internal/flags/index.js b/packages/svelte/src/internal/flags/index.js index ce7bba604b..5d4054975f 100644 --- a/packages/svelte/src/internal/flags/index.js +++ b/packages/svelte/src/internal/flags/index.js @@ -1,5 +1,8 @@ +/** True if experimental.async=true */ export let async_mode_flag = false; +/** True if we're not certain that we only have Svelte 5 code in the compilation */ export let legacy_mode_flag = false; +/** True if $inspect.trace is used */ export let tracing_mode_flag = false; export function enable_async_mode_flag() {