Skip to content

Commit 70ad1c7

Browse files
committed
chore: improve signal performance by reducing duplicate deps
1 parent 4aadb34 commit 70ad1c7

File tree

8 files changed

+57
-39
lines changed

8 files changed

+57
-39
lines changed

.changeset/tame-students-retire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: improve signal performance by reducing duplicate deps

packages/svelte/src/internal/client/dev/tracing.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ function log_entry(signal, entry) {
4343
const type = (signal.f & DERIVED) !== 0 ? '$derived' : '$state';
4444
const current_reaction = /** @type {Reaction} */ (active_reaction);
4545
const status =
46-
signal.version > current_reaction.version || current_reaction.version === 0 ? 'dirty' : 'clean';
46+
signal.w_version > current_reaction.w_version || current_reaction.w_version === 0
47+
? 'dirty'
48+
: 'clean';
4749

4850
// eslint-disable-next-line no-console
4951
console.groupCollapsed(

packages/svelte/src/internal/client/reactivity/deriveds.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
set_signal_status,
1717
skip_reaction,
1818
update_reaction,
19-
increment_version,
19+
increment_write_version,
2020
set_active_effect,
2121
component_context
2222
} from '../runtime.js';
@@ -57,9 +57,10 @@ export function derived(fn) {
5757
equals,
5858
f: flags,
5959
fn,
60+
r_version: 0,
6061
reactions: null,
6162
v: /** @type {V} */ (null),
62-
version: 0,
63+
w_version: 0,
6364
parent: parent_derived ?? active_effect
6465
};
6566

@@ -182,7 +183,7 @@ export function update_derived(derived) {
182183

183184
if (!derived.equals(value)) {
184185
derived.v = value;
185-
derived.version = increment_version();
186+
derived.w_version = increment_write_version();
186187
}
187188
}
188189

packages/svelte/src/internal/client/reactivity/effects.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ function create_effect(type, fn, sync, push = true) {
110110
prev: null,
111111
teardown: null,
112112
transitions: null,
113-
version: 0
113+
w_version: 0
114114
};
115115

116116
if (DEV) {

packages/svelte/src/internal/client/reactivity/sources.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
set_untracked_writes,
1313
set_signal_status,
1414
untrack,
15-
increment_version,
15+
increment_write_version,
1616
update_effect,
1717
derived_sources,
1818
set_derived_sources,
@@ -57,7 +57,8 @@ export function source(v, stack) {
5757
v,
5858
reactions: null,
5959
equals,
60-
version: 0
60+
r_version: 0,
61+
w_version: 0
6162
};
6263

6364
if (DEV && tracing_mode_flag) {
@@ -169,7 +170,7 @@ export function internal_set(source, value) {
169170
if (!source.equals(value)) {
170171
var old_value = source.v;
171172
source.v = value;
172-
source.version = increment_version();
173+
source.w_version = increment_write_version();
173174

174175
if (DEV && tracing_mode_flag) {
175176
source.updated = get_stack('UpdatedAt');

packages/svelte/src/internal/client/reactivity/types.d.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ export interface Signal {
44
/** Flags bitmask */
55
f: number;
66
/** Write version */
7-
version: number;
7+
w_version: number;
88
}
99

1010
export interface Value<V = unknown> extends Signal {
11-
/** Signals that read from this signal */
12-
reactions: null | Reaction[];
11+
created?: Error | null;
1312
/** Equality function */
1413
equals: Equals;
14+
/** Signals that read from this signal */
15+
reactions: null | Reaction[];
16+
/** Read version */
17+
r_version: number;
1518
/** The latest value for this signal */
1619
v: V;
1720
/** Dev only */
18-
created?: Error | null;
1921
updated?: Error | null;
2022
trace_need_increase?: boolean;
2123
trace_v?: V;
@@ -36,6 +38,8 @@ export interface Derived<V = unknown> extends Value<V>, Reaction {
3638
fn: () => V;
3739
/** Reactions created inside this signal */
3840
children: null | Reaction[];
41+
/** Read version */
42+
r_version: number;
3943
/** Parent effect or derived */
4044
parent: Effect | Derived | null;
4145
}

packages/svelte/src/internal/client/runtime.js

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ export function set_untracked_writes(value) {
128128
}
129129

130130
/** @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 */
131-
let current_version = 1;
131+
let write_version = 1;
132+
let read_version = 0;
132133

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

171-
export function increment_version() {
172-
return ++current_version;
172+
export function increment_write_version() {
173+
return ++write_version;
173174
}
174175

175176
/** @returns {boolean} */
@@ -226,7 +227,7 @@ export function check_dirtiness(reaction) {
226227
update_derived(/** @type {Derived} */ (dependency));
227228
}
228229

229-
if (dependency.version > reaction.version) {
230+
if (dependency.w_version > reaction.w_version) {
230231
return true;
231232
}
232233
}
@@ -398,6 +399,7 @@ export function update_reaction(reaction) {
398399
skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0;
399400
derived_sources = null;
400401
component_context = reaction.ctx;
402+
read_version++;
401403

402404
try {
403405
var result = /** @type {Function} */ (0, reaction.fn)();
@@ -528,7 +530,7 @@ export function update_effect(effect) {
528530
execute_effect_teardown(effect);
529531
var teardown = update_reaction(effect);
530532
effect.teardown = typeof teardown === 'function' ? teardown : null;
531-
effect.version = current_version;
533+
effect.w_version = write_version;
532534

533535
var deps = effect.deps;
534536

@@ -540,7 +542,7 @@ export function update_effect(effect) {
540542
for (let i = 0; i < deps.length; i++) {
541543
var dep = deps[i];
542544
if (dep.trace_need_increase) {
543-
dep.version = increment_version();
545+
dep.w_version = increment_write_version();
544546
dep.trace_need_increase = undefined;
545547
dep.trace_v = undefined;
546548
}
@@ -880,27 +882,29 @@ export function get(signal) {
880882
e.state_unsafe_local_read();
881883
}
882884
var deps = active_reaction.deps;
885+
if (signal.r_version < read_version) {
886+
signal.r_version = read_version;
887+
// If the signal is accessing the same dependencies in the same
888+
// order as it did last time, increment `skipped_deps`
889+
// rather than updating `new_deps`, which creates GC cost
890+
if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
891+
skipped_deps++;
892+
} else if (new_deps === null) {
893+
new_deps = [signal];
894+
} else {
895+
new_deps.push(signal);
896+
}
883897

884-
// If the signal is accessing the same dependencies in the same
885-
// order as it did last time, increment `skipped_deps`
886-
// rather than updating `new_deps`, which creates GC cost
887-
if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
888-
skipped_deps++;
889-
} else if (new_deps === null) {
890-
new_deps = [signal];
891-
} else {
892-
new_deps.push(signal);
893-
}
894-
895-
if (
896-
untracked_writes !== null &&
897-
active_effect !== null &&
898-
(active_effect.f & CLEAN) !== 0 &&
899-
(active_effect.f & BRANCH_EFFECT) === 0 &&
900-
untracked_writes.includes(signal)
901-
) {
902-
set_signal_status(active_effect, DIRTY);
903-
schedule_effect(active_effect);
898+
if (
899+
untracked_writes !== null &&
900+
active_effect !== null &&
901+
(active_effect.f & CLEAN) !== 0 &&
902+
(active_effect.f & BRANCH_EFFECT) === 0 &&
903+
untracked_writes.includes(signal)
904+
) {
905+
set_signal_status(active_effect, DIRTY);
906+
schedule_effect(active_effect);
907+
}
904908
}
905909
} else if (is_derived && /** @type {Derived} */ (signal).deps === null) {
906910
var derived = /** @type {Derived} */ (signal);

packages/svelte/tests/signals/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ describe('signals', () => {
296296
const destroy = effect_root(() => {
297297
user_effect(() => {
298298
log.push($.get(calc));
299+
$.get(calc);
299300
});
300301
});
301302

@@ -306,7 +307,7 @@ describe('signals', () => {
306307
flushSync(() => set(count, 4));
307308
flushSync(() => set(count, 0));
308309
// Ensure we're not leaking consumers
309-
assert.deepEqual(count.reactions?.length, 2);
310+
assert.deepEqual(count.reactions?.length, 1);
310311
assert.deepEqual(calc.reactions?.length, 1);
311312
assert.deepEqual(log, [0, 2, 'limit', 0]);
312313
destroy();

0 commit comments

Comments
 (0)