Skip to content

Performance: O(n^2) time-complexity with remove_reactions #14875

@AlbertMarashi

Description

@AlbertMarashi

Describe the bug

I'm building a sort of very custom use-case AI dataset editor which involves rendering approximately ~80k DOM nodes.

Occasionally, a full re-render is required, which leads to remove_reactions getting called for each "cell" in my grid I am rendering.

remove_reactions calls remove_reaction n times, which itself, performs an indexOf on the dependencies, leading to terribly slow re-renders.

image

Code responsible:

/**
* @template V
* @param {Reaction} signal
* @param {Value<V>} dependency
* @returns {void}
*/
function remove_reaction(signal, dependency) {
let reactions = dependency.reactions;
if (reactions !== null) {
var index = reactions.indexOf(signal);
if (index !== -1) {
var new_length = reactions.length - 1;
if (new_length === 0) {
reactions = dependency.reactions = null;
} else {
// Swap with last element and then remove.
reactions[index] = reactions[new_length];
reactions.pop();
}
}
}
// If the derived has no reactions, then we can disconnect it from the graph,
// allowing it to either reconnect in the future, or be GC'd by the VM.
if (
reactions === null &&
(dependency.f & DERIVED) !== 0 &&
// Destroying a child effect while updating a parent effect can cause a dependency to appear
// to be unused, when in fact it is used by the currently-updating parent. Checking `new_deps`
// allows us to skip the expensive work of disconnecting and immediately reconnecting it
(new_deps === null || !new_deps.includes(dependency))
) {
set_signal_status(dependency, MAYBE_DIRTY);
// If we are working with a derived that is owned by an effect, then mark it as being
// disconnected.
if ((dependency.f & (UNOWNED | DISCONNECTED)) === 0) {
dependency.f ^= DISCONNECTED;
}
remove_reactions(/** @type {Derived} **/ (dependency), 0);
}
}
/**
* @param {Reaction} signal
* @param {number} start_index
* @returns {void}
*/
export function remove_reactions(signal, start_index) {
var dependencies = signal.deps;
if (dependencies === null) return;
for (var i = start_index; i < dependencies.length; i++) {
remove_reaction(signal, dependencies[i]);
}
}

Reproduction

...

Logs

No response

System Info

System:
    OS: macOS 15.1
    CPU: (8) arm64 Apple M1 Pro
    Memory: 116.11 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.17.0 - ~/.nvm/versions/node/v20.17.0/bin/node
    npm: 10.8.2 - ~/.nvm/versions/node/v20.17.0/bin/npm
    pnpm: 9.7.0 - ~/Library/pnpm/pnpm
    bun: 1.1.39 - ~/.bun/bin/bun
  Browsers:
    Chrome: 131.0.6778.205
    Safari: 18.1
  npmPackages:
    svelte: ^5.16.0 => 5.16.0

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions