chore: improve internal performance of effect runtime (#10999)

* chore: improve internal performance of effect runtime

* add TODOs

* add TODOs
pull/11011/head
Dominic Gannaway 7 months ago committed by GitHub
parent b6fab1cb91
commit 8c4769db37
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -423,6 +423,8 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
}); });
} }
// TODO: would be good to avoid this closure in the case where we have no
// transitions at all. It would make it far more JIT friendly in the hot cases.
pause_effects(to_destroy, () => { pause_effects(to_destroy, () => {
state.items = b_items; state.items = b_items;
}); });

@ -274,9 +274,8 @@ export function destroy_effect(effect) {
} }
} }
effect.first = // `first` and `child` are nulled out in destroy_effect_children
effect.last = effect.next =
effect.next =
effect.prev = effect.prev =
effect.teardown = effect.teardown =
effect.ctx = effect.ctx =
@ -318,14 +317,17 @@ export function pause_effect(effect, callback = noop) {
export function pause_effects(effects, callback = noop) { export function pause_effects(effects, callback = noop) {
/** @type {import('#client').TransitionManager[]} */ /** @type {import('#client').TransitionManager[]} */
var transitions = []; var transitions = [];
var length = effects.length;
for (var effect of effects) { for (var i = 0; i < length; i++) {
pause_children(effect, transitions, true); pause_children(effects[i], transitions, true);
} }
// TODO: would be good to avoid this closure in the case where we have no
// transitions at all. It would make it far more JIT friendly in the hot cases.
out(transitions, () => { out(transitions, () => {
for (var effect of effects) { for (var i = 0; i < length; i++) {
destroy_effect(effect); destroy_effect(effects[i]);
} }
callback(); callback();
}); });
@ -370,6 +372,8 @@ function pause_children(effect, transitions, local) {
var sibling = child.next; var sibling = child.next;
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0; var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
// TODO we don't need to call pause_children recursively with a linked list in place // TODO we don't need to call pause_children recursively with a linked list in place
// it's slightly more involved though as we have to account for `transparent` changing
// through the tree.
pause_children(child, transitions, transparent ? local : false); pause_children(child, transitions, transparent ? local : false);
child = sibling; child = sibling;
} }
@ -404,6 +408,8 @@ function resume_children(effect, local) {
var sibling = child.next; var sibling = child.next;
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0; var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & BRANCH_EFFECT) !== 0;
// TODO we don't need to call resume_children recursively with a linked list in place // TODO we don't need to call resume_children recursively with a linked list in place
// it's slightly more involved though as we have to account for `transparent` changing
// through the tree.
resume_children(child, transparent ? local : false); resume_children(child, transparent ? local : false);
child = sibling; child = sibling;
} }

@ -484,72 +484,92 @@ export function schedule_effect(signal) {
/** /**
* *
* This function recursively collects effects in topological order from the starting effect passed in. * This function both runs render effects and collects user effects in topological order
* Effects will be collected when they match the filtered bitwise flag passed in only. The collected * from the starting effect passed in. Effects will be collected when they match the filtered
* array will be populated with all the effects. * bitwise flag passed in only. The collected effects array will be populated with all the user
* * effects to be flushed.
* In an ideal world, we could just execute effects as we encounter them using this approach. However,
* this isn't possible due to how effects in Svelte are modelled to be possibly side-effectful. Thus,
* executing an effect might invalidate other parts of the tree, which means this this tree walking function
* will possibly pick up effects that are dirty too soon.
* *
* @param {import('./types.js').Effect} effect * @param {import('./types.js').Effect} effect
* @param {number} filter_flags * @param {number} filter_flags
* @param {boolean} shallow * @param {boolean} shallow
* @param {import('./types.js').Effect[]} collected_user * @param {import('./types.js').Effect[]} collected_effects
* @returns {void} * @returns {void}
*/ */
function recursively_process_effects(effect, filter_flags, shallow, collected_user) { function process_effects(effect, filter_flags, shallow, collected_effects) {
var current_child = effect.first; var current_effect = effect.first;
var user = []; var effects = [];
while (current_child !== null) { main_loop: while (current_effect !== null) {
var child = current_child; var flags = current_effect.f;
current_child = child.next; // TODO: we probably don't need to check for destroyed as it shouldn't be encountered?
var flags = child.f; var is_active = (flags & (DESTROYED | INERT)) === 0;
var is_inactive = (flags & (DESTROYED | INERT)) !== 0;
if (is_inactive) continue;
var is_branch = flags & BRANCH_EFFECT; var is_branch = flags & BRANCH_EFFECT;
var is_clean = (flags & CLEAN) !== 0; var is_clean = (flags & CLEAN) !== 0;
var child = current_effect.first;
if (is_branch) { // Skip this branch if it's clean
// Skip this branch if it's clean if (is_active && (!is_branch || !is_clean)) {
if (is_clean) continue;
set_signal_status(child, CLEAN);
}
if ((flags & RENDER_EFFECT) !== 0) {
if (is_branch) { if (is_branch) {
if (shallow) continue; set_signal_status(current_effect, CLEAN);
// TODO we don't need to call recursively_process_effects recursively with a linked list in place }
recursively_process_effects(child, filter_flags, false, collected_user);
} else { if ((flags & RENDER_EFFECT) !== 0) {
if (check_dirtiness(child)) { if (is_branch) {
execute_effect(child); if (!shallow && child !== null) {
current_effect = child;
continue;
}
} else {
if (check_dirtiness(current_effect)) {
execute_effect(current_effect);
// Child might have been mutated since running the effect
child = current_effect.first;
}
if (!shallow && child !== null) {
current_effect = child;
continue;
}
}
} else if ((flags & EFFECT) !== 0) {
if (is_branch || is_clean) {
if (!shallow && child !== null) {
current_effect = child;
continue;
}
} else {
effects.push(current_effect);
} }
// TODO we don't need to call recursively_process_effects recursively with a linked list in place
recursively_process_effects(child, filter_flags, false, collected_user);
} }
} else if ((flags & EFFECT) !== 0) { }
if (is_branch || is_clean) { var sibling = current_effect.next;
if (shallow) continue;
// TODO we don't need to call recursively_process_effects recursively with a linked list in place if (sibling === null) {
recursively_process_effects(child, filter_flags, false, collected_user); let parent = current_effect.parent;
} else {
user.push(child); while (parent !== null) {
if (effect === parent) {
break main_loop;
}
var parent_sibling = parent.next;
if (parent_sibling !== null) {
current_effect = parent_sibling;
continue main_loop;
}
parent = parent.parent;
} }
} }
current_effect = sibling;
} }
if (user.length > 0) { if (effects.length > 0) {
if ((filter_flags & EFFECT) !== 0) { if ((filter_flags & EFFECT) !== 0) {
collected_user.push(...user); collected_effects.push(...effects);
} }
if (!shallow) { if (!shallow) {
for (var i = 0; i < user.length; i++) { for (var i = 0; i < effects.length; i++) {
// TODO we don't need to call recursively_process_effects recursively with a linked list in place process_effects(effects[i], filter_flags, false, collected_effects);
recursively_process_effects(user[i], filter_flags, false, collected_user);
} }
} }
} }
@ -568,7 +588,7 @@ function recursively_process_effects(effect, filter_flags, shallow, collected_us
*/ */
function flush_nested_effects(effect, filter_flags, shallow = false) { function flush_nested_effects(effect, filter_flags, shallow = false) {
/** @type {import('#client').Effect[]} */ /** @type {import('#client').Effect[]} */
var user_effects = []; var collected_effects = [];
var previously_flushing_effect = is_flushing_effect; var previously_flushing_effect = is_flushing_effect;
is_flushing_effect = true; is_flushing_effect = true;
@ -578,8 +598,8 @@ function flush_nested_effects(effect, filter_flags, shallow = false) {
if (effect.first === null && (effect.f & BRANCH_EFFECT) === 0) { if (effect.first === null && (effect.f & BRANCH_EFFECT) === 0) {
flush_queued_effects([effect]); flush_queued_effects([effect]);
} else { } else {
recursively_process_effects(effect, filter_flags, shallow, user_effects); process_effects(effect, filter_flags, shallow, collected_effects);
flush_queued_effects(user_effects); flush_queued_effects(collected_effects);
} }
} finally { } finally {
is_flushing_effect = previously_flushing_effect; is_flushing_effect = previously_flushing_effect;

Loading…
Cancel
Save