Skip to content

Commit e901976

Browse files
committed
feat: sort eager_block_effects by depth before updating
1 parent 3818b9e commit e901976

File tree

3 files changed

+231
-2
lines changed

3 files changed

+231
-2
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { fastest_test } from '../../utils.js';
2+
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
3+
4+
const COUNT = 1e5;
5+
6+
/**
7+
* @param {number} n
8+
* @param {any[]} sources
9+
*/
10+
function create_data_signals(n, sources) {
11+
for (let i = 0; i < n; i++) {
12+
sources[i] = $.state(i % 2 === 0); // alternate true/false for conditions
13+
}
14+
return sources;
15+
}
16+
17+
/**
18+
* @param {any} condition
19+
* @param {any} consequent
20+
*/
21+
function create_if_block(condition, consequent) {
22+
const anchor = $.comment();
23+
$.if(anchor, (branch) => {
24+
if ($.get(condition)) {
25+
branch(() => {
26+
$.get(consequent);
27+
});
28+
}
29+
});
30+
}
31+
32+
/**
33+
* @param {any} condition
34+
* @param {any} consequent
35+
* @param {any} alternate
36+
*/
37+
function create_if_else_block(condition, consequent, alternate) {
38+
const anchor = $.comment();
39+
$.if(anchor, (branch) => {
40+
if ($.get(condition)) {
41+
branch(() => {
42+
$.get(consequent);
43+
});
44+
} else {
45+
branch(() => {
46+
$.get(alternate);
47+
}, false);
48+
}
49+
});
50+
}
51+
52+
/**
53+
* @param {number} n
54+
* @param {any[]} sources
55+
*/
56+
function create_if_blocks(n, sources) {
57+
for (let i = 0; i < n; i++) {
58+
const condition = sources[i * 2];
59+
const consequent = sources[i * 2 + 1];
60+
create_if_block(condition, consequent);
61+
}
62+
}
63+
64+
/**
65+
* @param {number} n
66+
* @param {any[]} sources
67+
*/
68+
function create_if_else_blocks(n, sources) {
69+
for (let i = 0; i < n; i++) {
70+
const condition = sources[i * 3];
71+
const consequent = sources[i * 3 + 1];
72+
const alternate = sources[i * 3 + 2];
73+
create_if_else_block(condition, consequent, alternate);
74+
}
75+
}
76+
77+
/**
78+
* @param {any[]} sources
79+
*/
80+
function toggle_conditions(sources) {
81+
for (let i = 0; i < sources.length; i++) {
82+
$.set(sources[i], !$.get(sources[i]));
83+
}
84+
}
85+
86+
/**
87+
* @param {any} fn
88+
* @param {number} count
89+
* @param {number} scount
90+
*/
91+
function bench(fn, count, scount) {
92+
let sources = create_data_signals(scount, []);
93+
fn(count, sources);
94+
}
95+
96+
export async function if_blocks_create() {
97+
// Do 3 loops to warm up JIT
98+
for (let i = 0; i < 3; i++) {
99+
bench(create_if_blocks, COUNT / 2, COUNT);
100+
}
101+
102+
const { timing } = await fastest_test(10, () => {
103+
const destroy = $.effect_root(() => {
104+
for (let i = 0; i < 10; i++) {
105+
bench(create_if_blocks, COUNT / 2, COUNT);
106+
}
107+
});
108+
destroy();
109+
});
110+
111+
return {
112+
benchmark: 'if_blocks_create',
113+
time: timing.time.toFixed(2),
114+
gc_time: timing.gc_time.toFixed(2)
115+
};
116+
}
117+
118+
export async function if_else_blocks_create() {
119+
// Do 3 loops to warm up JIT
120+
for (let i = 0; i < 3; i++) {
121+
bench(create_if_else_blocks, COUNT / 3, COUNT);
122+
}
123+
124+
const { timing } = await fastest_test(10, () => {
125+
const destroy = $.effect_root(() => {
126+
for (let i = 0; i < 10; i++) {
127+
bench(create_if_else_blocks, COUNT / 3, COUNT);
128+
}
129+
});
130+
destroy();
131+
});
132+
133+
return {
134+
benchmark: 'if_else_blocks_create',
135+
time: timing.time.toFixed(2),
136+
gc_time: timing.gc_time.toFixed(2)
137+
};
138+
}
139+
140+
export async function if_blocks_update() {
141+
// Create blocks once, then toggle conditions
142+
const conditions = [];
143+
create_data_signals(1000, conditions);
144+
145+
// Do 3 loops to warm up JIT
146+
for (let i = 0; i < 3; i++) {
147+
const destroy = $.effect_root(() => {
148+
create_if_blocks(500, conditions);
149+
});
150+
toggle_conditions(conditions.slice(0, 1000).filter((_, i) => i % 2 === 0));
151+
destroy();
152+
}
153+
154+
const { timing } = await fastest_test(10, () => {
155+
const destroy = $.effect_root(() => {
156+
create_if_blocks(500, conditions);
157+
for (let i = 0; i < 100; i++) {
158+
toggle_conditions(conditions.slice(0, 1000).filter((_, i) => i % 2 === 0));
159+
}
160+
});
161+
destroy();
162+
});
163+
164+
return {
165+
benchmark: 'if_blocks_update',
166+
time: timing.time.toFixed(2),
167+
gc_time: timing.gc_time.toFixed(2)
168+
};
169+
}
170+
171+
export async function if_else_blocks_update() {
172+
// Create blocks once, then toggle conditions
173+
const sources = [];
174+
create_data_signals(1500, sources);
175+
176+
// Do 3 loops to warm up JIT
177+
for (let i = 0; i < 3; i++) {
178+
const destroy = $.effect_root(() => {
179+
create_if_else_blocks(500, sources);
180+
});
181+
toggle_conditions(sources.slice(0, 1500).filter((_, i) => i % 3 === 0));
182+
destroy();
183+
}
184+
185+
const { timing } = await fastest_test(10, () => {
186+
const destroy = $.effect_root(() => {
187+
create_if_else_blocks(500, sources);
188+
for (let i = 0; i < 100; i++) {
189+
toggle_conditions(sources.slice(0, 1500).filter((_, i) => i % 3 === 0));
190+
}
191+
});
192+
destroy();
193+
});
194+
195+
return {
196+
benchmark: 'if_else_blocks_update',
197+
time: timing.time.toFixed(2),
198+
gc_time: timing.gc_time.toFixed(2)
199+
};
200+
}

benchmarking/benchmarks/reactivity/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import {
1919
sbench_create_4to1,
2020
sbench_create_signals
2121
} from './sbench.js';
22+
import {
23+
if_blocks_create,
24+
if_blocks_update,
25+
if_else_blocks_create,
26+
if_else_blocks_update
27+
} from './if_blocks.js';
2228

2329
// This benchmark has been adapted from the js-reactivity-benchmark (https://github.com/milomg/js-reactivity-benchmark)
2430
// Not all tests are the same, and many parts have been tweaked to capture different data.
@@ -34,6 +40,10 @@ export const reactivity_benchmarks = [
3440
sbench_create_1to4,
3541
sbench_create_1to8,
3642
sbench_create_1to1000,
43+
if_blocks_create,
44+
if_blocks_update,
45+
if_else_blocks_create,
46+
if_else_blocks_update,
3747
kairo_avoidable_owned,
3848
kairo_avoidable_unowned,
3949
kairo_broad_owned,

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,9 +626,28 @@ function flush_queued_effects(effects) {
626626
// TODO this feels incorrect! it gets the tests passing
627627
old_values.clear();
628628

629+
/**
630+
* @type {Array<{ effect: Effect; depth: number }>} sorted
631+
*/
632+
const sorted = Array(eager_block_effects.length);
633+
for (let j = 0; j < eager_block_effects.length; j++) {
634+
var depth = 0;
635+
var parent = eager_block_effects[j].parent;
636+
637+
while (parent !== null) {
638+
depth++;
639+
parent = parent.parent;
640+
}
641+
642+
sorted[j] = { effect: eager_block_effects[j], depth };
643+
}
644+
645+
// Sort by depth
646+
sorted.sort((a, b) => a.depth - b.depth);
647+
629648
// Update effects in reverse order to ensure outer guards are processed before their contents
630-
for (let i = eager_block_effects.length - 1; i >= 0; i--) {
631-
update_effect(eager_block_effects[i]);
649+
for (const e of sorted) {
650+
update_effect(e.effect);
632651
}
633652

634653
eager_block_effects = [];

0 commit comments

Comments
 (0)