Skip to content

Commit

Permalink
chore: improve signal performance by reducing duplicate deps (#14945)
Browse files Browse the repository at this point in the history
* chore: expand benchmark iterations

* chore: improve signal performance by reducing duplicate deps

* feedback

* oops
  • Loading branch information
trueadm authored Jan 8, 2025
1 parent deb362f commit 9c20eb4
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/tame-students-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: improve signal performance by reducing duplicate deps
7 changes: 2 additions & 5 deletions packages/svelte/src/internal/client/dev/tracing.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,12 @@ function log_entry(signal, entry) {

const type = (signal.f & DERIVED) !== 0 ? '$derived' : '$state';
const current_reaction = /** @type {Reaction} */ (active_reaction);
const status =
signal.version > current_reaction.version || current_reaction.version === 0 ? 'dirty' : 'clean';
const dirty = signal.wv > current_reaction.wv || current_reaction.wv === 0;

// eslint-disable-next-line no-console
console.groupCollapsed(
`%c${type}`,
status !== 'clean'
? 'color: CornflowerBlue; font-weight: bold'
: 'color: grey; font-weight: bold',
dirty ? 'color: CornflowerBlue; font-weight: bold' : 'color: grey; font-weight: bold',
typeof value === 'object' && value !== null && STATE_SYMBOL in value
? snapshot(value, true)
: value
Expand Down
7 changes: 4 additions & 3 deletions packages/svelte/src/internal/client/reactivity/deriveds.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
set_signal_status,
skip_reaction,
update_reaction,
increment_version,
increment_write_version,
set_active_effect,
component_context
} from '../runtime.js';
Expand Down Expand Up @@ -58,8 +58,9 @@ export function derived(fn) {
f: flags,
fn,
reactions: null,
rv: 0,
v: /** @type {V} */ (null),
version: 0,
wv: 0,
parent: parent_derived ?? active_effect
};

Expand Down Expand Up @@ -182,7 +183,7 @@ export function update_derived(derived) {

if (!derived.equals(value)) {
derived.v = value;
derived.version = increment_version();
derived.wv = increment_write_version();
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/reactivity/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function create_effect(type, fn, sync, push = true) {
prev: null,
teardown: null,
transitions: null,
version: 0
wv: 0
};

if (DEV) {
Expand Down
7 changes: 4 additions & 3 deletions packages/svelte/src/internal/client/reactivity/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
set_untracked_writes,
set_signal_status,
untrack,
increment_version,
increment_write_version,
update_effect,
derived_sources,
set_derived_sources,
Expand Down Expand Up @@ -57,7 +57,8 @@ export function source(v, stack) {
v,
reactions: null,
equals,
version: 0
rv: 0,
wv: 0
};

if (DEV && tracing_mode_flag) {
Expand Down Expand Up @@ -169,7 +170,7 @@ export function internal_set(source, value) {
if (!source.equals(value)) {
var old_value = source.v;
source.v = value;
source.version = increment_version();
source.wv = increment_write_version();

if (DEV && tracing_mode_flag) {
source.updated = get_stack('UpdatedAt');
Expand Down
8 changes: 5 additions & 3 deletions packages/svelte/src/internal/client/reactivity/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ export interface Signal {
/** Flags bitmask */
f: number;
/** Write version */
version: number;
wv: number;
}

export interface Value<V = unknown> extends Signal {
/** Signals that read from this signal */
reactions: null | Reaction[];
/** Equality function */
equals: Equals;
/** Signals that read from this signal */
reactions: null | Reaction[];
/** Read version */
rv: number;
/** The latest value for this signal */
v: V;
/** Dev only */
Expand Down
63 changes: 36 additions & 27 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,14 @@ export function set_untracked_writes(value) {
untracked_writes = value;
}

/** @type {number} Used by sources and deriveds for handling updates to unowned deriveds it starts from 1 to differentiate between a created effect and a run one for tracing */
let current_version = 1;
/**
* @type {number} Used by sources and deriveds for handling updates.
* Version starts from 1 so that unowned deriveds differentiate between a created effect and a run one for tracing
**/
let write_version = 1;

/** @type {number} Used to version each read of a source of derived to avoid duplicating depedencies inside a reaction */
let read_version = 0;

// If we are working with a get() chain that has no active container,
// to prevent memory leaks, we skip adding the reaction.
Expand Down Expand Up @@ -168,8 +174,8 @@ export function set_dev_current_component_function(fn) {
dev_current_component_function = fn;
}

export function increment_version() {
return ++current_version;
export function increment_write_version() {
return ++write_version;
}

/** @returns {boolean} */
Expand Down Expand Up @@ -226,7 +232,7 @@ export function check_dirtiness(reaction) {
update_derived(/** @type {Derived} */ (dependency));
}

if (dependency.version > reaction.version) {
if (dependency.wv > reaction.wv) {
return true;
}
}
Expand Down Expand Up @@ -398,6 +404,7 @@ export function update_reaction(reaction) {
skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0;
derived_sources = null;
component_context = reaction.ctx;
read_version++;

try {
var result = /** @type {Function} */ (0, reaction.fn)();
Expand Down Expand Up @@ -528,7 +535,7 @@ export function update_effect(effect) {
execute_effect_teardown(effect);
var teardown = update_reaction(effect);
effect.teardown = typeof teardown === 'function' ? teardown : null;
effect.version = current_version;
effect.wv = write_version;

var deps = effect.deps;

Expand All @@ -540,7 +547,7 @@ export function update_effect(effect) {
for (let i = 0; i < deps.length; i++) {
var dep = deps[i];
if (dep.trace_need_increase) {
dep.version = increment_version();
dep.wv = increment_write_version();
dep.trace_need_increase = undefined;
dep.trace_v = undefined;
}
Expand Down Expand Up @@ -880,27 +887,29 @@ export function get(signal) {
e.state_unsafe_local_read();
}
var deps = active_reaction.deps;
if (signal.rv < read_version) {
signal.rv = read_version;
// If the signal is accessing the same dependencies in the same
// order as it did last time, increment `skipped_deps`
// rather than updating `new_deps`, which creates GC cost
if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
skipped_deps++;
} else if (new_deps === null) {
new_deps = [signal];
} else {
new_deps.push(signal);
}

// If the signal is accessing the same dependencies in the same
// order as it did last time, increment `skipped_deps`
// rather than updating `new_deps`, which creates GC cost
if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
skipped_deps++;
} else if (new_deps === null) {
new_deps = [signal];
} else {
new_deps.push(signal);
}

if (
untracked_writes !== null &&
active_effect !== null &&
(active_effect.f & CLEAN) !== 0 &&
(active_effect.f & BRANCH_EFFECT) === 0 &&
untracked_writes.includes(signal)
) {
set_signal_status(active_effect, DIRTY);
schedule_effect(active_effect);
if (
untracked_writes !== null &&
active_effect !== null &&
(active_effect.f & CLEAN) !== 0 &&
(active_effect.f & BRANCH_EFFECT) === 0 &&
untracked_writes.includes(signal)
) {
set_signal_status(active_effect, DIRTY);
schedule_effect(active_effect);
}
}
} else if (is_derived && /** @type {Derived} */ (signal).deps === null) {
var derived = /** @type {Derived} */ (signal);
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/tests/signals/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ describe('signals', () => {
const destroy = effect_root(() => {
user_effect(() => {
log.push($.get(calc));
$.get(calc);
});
});

Expand All @@ -306,7 +307,7 @@ describe('signals', () => {
flushSync(() => set(count, 4));
flushSync(() => set(count, 0));
// Ensure we're not leaking consumers
assert.deepEqual(count.reactions?.length, 2);
assert.deepEqual(count.reactions?.length, 1);
assert.deepEqual(calc.reactions?.length, 1);
assert.deepEqual(log, [0, 2, 'limit', 0]);
destroy();
Expand Down

0 comments on commit 9c20eb4

Please sign in to comment.