From 67090e8ee8a4ac3cb2926969f75cf161ace6367d Mon Sep 17 00:00:00 2001 From: Mathias Picker <48158184+MathiasWP@users.noreply.github.com> Date: Thu, 28 May 2026 13:53:13 +0200 Subject: [PATCH] perf: store current_sources as Set for O(1) membership checks (#18278) `current_sources` tracks the sources created within the active reaction so that reading or writing them during that same reaction doesn't trigger a re-run. It was an `Array` checked with `Array.prototype.includes.call(...)` in three hot places: the `state_unsafe_mutation` guard, `set()`'s destruction check, and `schedule_possible_effect_self_invalidation`. --------- Co-authored-by: Rich Harris --- .changeset/current-sources-set.md | 5 +++++ .../svelte/src/internal/client/reactivity/sources.js | 3 +-- packages/svelte/src/internal/client/runtime.js | 12 ++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 .changeset/current-sources-set.md diff --git a/.changeset/current-sources-set.md b/.changeset/current-sources-set.md new file mode 100644 index 0000000000..e1a4dc43b2 --- /dev/null +++ b/.changeset/current-sources-set.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +perf: store `current_sources` as a `Set` for O(1) membership checks diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index f374f6a26b..218941ab67 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -32,7 +32,6 @@ import { } from '#client/constants'; import * as e from '../errors.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; -import { includes } from '../../shared/utils.js'; import { tag_proxy } from '../dev/tracing.js'; import { get_error } from '../../shared/dev.js'; import { component_context, is_runes } from '../context.js'; @@ -158,7 +157,7 @@ export function set(source, value, should_proxy = false) { (!untracking || (active_reaction.f & EAGER_EFFECT) !== 0) && is_runes() && (active_reaction.f & (DERIVED | BLOCK_EFFECT | ASYNC | EAGER_EFFECT)) !== 0 && - (current_sources === null || !includes.call(current_sources, source)) + (current_sources === null || !current_sources.has(source)) ) { e.state_unsafe_mutation(); } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 87abbd71b6..8d70eee43c 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -90,18 +90,14 @@ export function set_active_effect(effect) { /** * When sources are created within a reaction, reading and writing * them within that reaction should not cause a re-run - * @type {null | Source[]} + * @type {null | Set} */ export let current_sources = null; /** @param {Value} value */ export function push_reaction_value(value) { if (active_reaction !== null && (!async_mode_flag || (active_reaction.f & DERIVED) !== 0)) { - if (current_sources === null) { - current_sources = [value]; - } else { - current_sources.push(value); - } + (current_sources ??= new Set()).add(value); } } @@ -202,7 +198,7 @@ function schedule_possible_effect_self_invalidation(signal, effect, root = true) var reactions = signal.reactions; if (reactions === null) return; - if (!async_mode_flag && current_sources !== null && includes.call(current_sources, signal)) { + if (!async_mode_flag && current_sources !== null && current_sources.has(signal)) { return; } @@ -540,7 +536,7 @@ export function get(signal) { // we don't add the dependency, because that would create a memory leak var destroyed = active_effect !== null && (active_effect.f & DESTROYED) !== 0; - if (!destroyed && (current_sources === null || !includes.call(current_sources, signal))) { + if (!destroyed && (current_sources === null || !current_sources.has(signal))) { var deps = active_reaction.deps; if ((active_reaction.f & REACTION_IS_UPDATING) !== 0) {