Skip to content

Commit 8c4769d

Browse files
authored
chore: improve internal performance of effect runtime (#10999)
* chore: improve internal performance of effect runtime * add TODOs * add TODOs
1 parent b6fab1c commit 8c4769d

File tree

3 files changed

+84
-56
lines changed

3 files changed

+84
-56
lines changed

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,8 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
423423
});
424424
}
425425

426+
// TODO: would be good to avoid this closure in the case where we have no
427+
// transitions at all. It would make it far more JIT friendly in the hot cases.
426428
pause_effects(to_destroy, () => {
427429
state.items = b_items;
428430
});

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,8 @@ export function destroy_effect(effect) {
274274
}
275275
}
276276

277-
effect.first =
278-
effect.last =
279-
effect.next =
277+
// `first` and `child` are nulled out in destroy_effect_children
278+
effect.next =
280279
effect.prev =
281280
effect.teardown =
282281
effect.ctx =
@@ -318,14 +317,17 @@ export function pause_effect(effect, callback = noop) {
318317
export function pause_effects(effects, callback = noop) {
319318
/** @type {import('#client').TransitionManager[]} */
320319
var transitions = [];
320+
var length = effects.length;
321321

322-
for (var effect of effects) {
323-
pause_children(effect, transitions, true);
322+
for (var i = 0; i < length; i++) {
323+
pause_children(effects[i], transitions, true);
324324
}
325325

326+
// TODO: would be good to avoid this closure in the case where we have no
327+
// transitions at all. It would make it far more JIT friendly in the hot cases.
326328
out(transitions, () => {
327-
for (var effect of effects) {
328-
destroy_effect(effect);
329+
for (var i = 0; i < length; i++) {
330+
destroy_effect(effects[i]);
329331
}
330332
callback();
331333
});
@@ -370,6 +372,8 @@ function pause_children(effect, transitions, local) {
370372
var sibling = child.next;
371373
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
372374
// TODO we don't need to call pause_children recursively with a linked list in place
375+
// it's slightly more involved though as we have to account for `transparent` changing
376+
// through the tree.
373377
pause_children(child, transitions, transparent ? local : false);
374378
child = sibling;
375379
}
@@ -404,6 +408,8 @@ function resume_children(effect, local) {
404408
var sibling = child.next;
405409
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
406410
// TODO we don't need to call resume_children recursively with a linked list in place
411+
// it's slightly more involved though as we have to account for `transparent` changing
412+
// through the tree.
407413
resume_children(child, transparent ? local : false);
408414
child = sibling;
409415
}

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

Lines changed: 69 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -484,72 +484,92 @@ export function schedule_effect(signal) {
484484

485485
/**
486486
*
487-
* This function recursively collects effects in topological order from the starting effect passed in.
488-
* Effects will be collected when they match the filtered bitwise flag passed in only. The collected
489-
* array will be populated with all the effects.
490-
*
491-
* In an ideal world, we could just execute effects as we encounter them using this approach. However,
492-
* this isn't possible due to how effects in Svelte are modelled to be possibly side-effectful. Thus,
493-
* executing an effect might invalidate other parts of the tree, which means this this tree walking function
494-
* will possibly pick up effects that are dirty too soon.
487+
* This function both runs render effects and collects user effects in topological order
488+
* from the starting effect passed in. Effects will be collected when they match the filtered
489+
* bitwise flag passed in only. The collected effects array will be populated with all the user
490+
* effects to be flushed.
495491
*
496492
* @param {import('./types.js').Effect} effect
497493
* @param {number} filter_flags
498494
* @param {boolean} shallow
499-
* @param {import('./types.js').Effect[]} collected_user
495+
* @param {import('./types.js').Effect[]} collected_effects
500496
* @returns {void}
501497
*/
502-
function recursively_process_effects(effect, filter_flags, shallow, collected_user) {
503-
var current_child = effect.first;
504-
var user = [];
505-
506-
while (current_child !== null) {
507-
var child = current_child;
508-
current_child = child.next;
509-
var flags = child.f;
510-
var is_inactive = (flags & (DESTROYED | INERT)) !== 0;
511-
if (is_inactive) continue;
498+
function process_effects(effect, filter_flags, shallow, collected_effects) {
499+
var current_effect = effect.first;
500+
var effects = [];
501+
502+
main_loop: while (current_effect !== null) {
503+
var flags = current_effect.f;
504+
// TODO: we probably don't need to check for destroyed as it shouldn't be encountered?
505+
var is_active = (flags & (DESTROYED | INERT)) === 0;
512506
var is_branch = flags & BRANCH_EFFECT;
513507
var is_clean = (flags & CLEAN) !== 0;
508+
var child = current_effect.first;
514509

515-
if (is_branch) {
516-
// Skip this branch if it's clean
517-
if (is_clean) continue;
518-
set_signal_status(child, CLEAN);
519-
}
520-
521-
if ((flags & RENDER_EFFECT) !== 0) {
510+
// Skip this branch if it's clean
511+
if (is_active && (!is_branch || !is_clean)) {
522512
if (is_branch) {
523-
if (shallow) continue;
524-
// TODO we don't need to call recursively_process_effects recursively with a linked list in place
525-
recursively_process_effects(child, filter_flags, false, collected_user);
526-
} else {
527-
if (check_dirtiness(child)) {
528-
execute_effect(child);
513+
set_signal_status(current_effect, CLEAN);
514+
}
515+
516+
if ((flags & RENDER_EFFECT) !== 0) {
517+
if (is_branch) {
518+
if (!shallow && child !== null) {
519+
current_effect = child;
520+
continue;
521+
}
522+
} else {
523+
if (check_dirtiness(current_effect)) {
524+
execute_effect(current_effect);
525+
// Child might have been mutated since running the effect
526+
child = current_effect.first;
527+
}
528+
if (!shallow && child !== null) {
529+
current_effect = child;
530+
continue;
531+
}
532+
}
533+
} else if ((flags & EFFECT) !== 0) {
534+
if (is_branch || is_clean) {
535+
if (!shallow && child !== null) {
536+
current_effect = child;
537+
continue;
538+
}
539+
} else {
540+
effects.push(current_effect);
529541
}
530-
// TODO we don't need to call recursively_process_effects recursively with a linked list in place
531-
recursively_process_effects(child, filter_flags, false, collected_user);
532542
}
533-
} else if ((flags & EFFECT) !== 0) {
534-
if (is_branch || is_clean) {
535-
if (shallow) continue;
536-
// TODO we don't need to call recursively_process_effects recursively with a linked list in place
537-
recursively_process_effects(child, filter_flags, false, collected_user);
538-
} else {
539-
user.push(child);
543+
}
544+
var sibling = current_effect.next;
545+
546+
if (sibling === null) {
547+
let parent = current_effect.parent;
548+
549+
while (parent !== null) {
550+
if (effect === parent) {
551+
break main_loop;
552+
}
553+
var parent_sibling = parent.next;
554+
if (parent_sibling !== null) {
555+
current_effect = parent_sibling;
556+
continue main_loop;
557+
}
558+
parent = parent.parent;
540559
}
541560
}
561+
562+
current_effect = sibling;
542563
}
543564

544-
if (user.length > 0) {
565+
if (effects.length > 0) {
545566
if ((filter_flags & EFFECT) !== 0) {
546-
collected_user.push(...user);
567+
collected_effects.push(...effects);
547568
}
548569

549570
if (!shallow) {
550-
for (var i = 0; i < user.length; i++) {
551-
// TODO we don't need to call recursively_process_effects recursively with a linked list in place
552-
recursively_process_effects(user[i], filter_flags, false, collected_user);
571+
for (var i = 0; i < effects.length; i++) {
572+
process_effects(effects[i], filter_flags, false, collected_effects);
553573
}
554574
}
555575
}
@@ -568,7 +588,7 @@ function recursively_process_effects(effect, filter_flags, shallow, collected_us
568588
*/
569589
function flush_nested_effects(effect, filter_flags, shallow = false) {
570590
/** @type {import('#client').Effect[]} */
571-
var user_effects = [];
591+
var collected_effects = [];
572592

573593
var previously_flushing_effect = is_flushing_effect;
574594
is_flushing_effect = true;
@@ -578,8 +598,8 @@ function flush_nested_effects(effect, filter_flags, shallow = false) {
578598
if (effect.first === null && (effect.f & BRANCH_EFFECT) === 0) {
579599
flush_queued_effects([effect]);
580600
} else {
581-
recursively_process_effects(effect, filter_flags, shallow, user_effects);
582-
flush_queued_effects(user_effects);
601+
process_effects(effect, filter_flags, shallow, collected_effects);
602+
flush_queued_effects(collected_effects);
583603
}
584604
} finally {
585605
is_flushing_effect = previously_flushing_effect;

0 commit comments

Comments
 (0)