-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Description
Describe the bug
We are developing a highly interactive application that displays a lot of interconnected data on the screen simultaneously. That's why our reactive graph is somewhat deep. Mostly Svelte is working amazingly for us and performance is great! However we've found one case where updating the graph taking so long, that our app start dropping frames.
All the following flamecharts are taken on a 4x CPU slowdown. That's to emulate more realistic user device as well as to make problems more visible. However without throttling there are occasional frame drops as well.
Here is a pick on how our flamechart does look like. The whole task here takes almost 78ms.

If we zoom in a bit, we can see lots of check_dirtiness calls.

This first 2 screens also got me wondering if it'd be possible to split process_deferred calls into multiple frames inside Svelte? That'd make for better scalability even on the big graphs.
And here is part of another task also taking 70ms+.

When zoomed it also should a lot of check_dirtiness calls.

Reproduction
Unfortunately making a reproduction seems to be really tricky and I am still working on it. Meanwhile I am looking for any help or suggestions on how to arrive at it. I know a high quality report would be much better and I'm looking to improve this one hopefully with your help.
Simplified our code looks somewhat like the following. We have a sorted list of items which we derive based on several fields values. Of course it's not the most efficient way to allocate an array and sort it on every change here, but it keeps a lot of things very simple (basically for the same reasons deriveds are advised over effects in docs). And I'd expect it to be performance bottleneck with large lists of items - like thousands or even tens of thousands (maybe I am wrong here). In our case frames seems to be dropped even when size is less than a hundred.
import { SvelteMap } from 'svelte/reactivity';
export class Store {
cache: SvelteMap<number, Model> = new SvelteMap();
constructor() {
for (let i = 1; i <= 500; i++) {
const model = new Model(i);
this.cache.set(i, model);
}
}
readonly all: Model[] = $derived.by(() => {
return Array.from(this.cache.values()).sort((a, b) => b.maxTime - a.maxTime);
});
readonly even: Model[] = $derived.by(() => {
return this.all.filter((model) => model.id % 2 === 0);
});
}
export class Model {
time: number = $state(0);
updateTime: number | null = $state(null);
constructor(readonly id: number) {
this.time = Date.now();
}
moveUp() {
this.updateTime = Date.now();
}
maxTime = $derived.by(() => {
return Math.max(this.time, this.updateTime || 0);
});
}<script lang="ts">
import type { Store } from '$lib/Store.svelte';
import { getContext } from 'svelte';
import { flip } from 'svelte/animate';
const store: Store = getContext('store');
</script>
<div>
<div>Total: {store.all.length}, Even: {store.even.length}</div>
{#each store.all as model, i (model.id)}
{@const next = store.all[i + 1]}
<div animate:flip>
{model.id.toString().padStart(4, '0')}{' '}<button onclick={() => model.moveUp()}
>click</button
>
{#if !next}
<div>that's it</div>
{/if}
</div>
{/each}
</div>
Logs
No response
System Info
MacBook Pro M1 2020, 16GB RAM
Chrome 131 with 4x CPU throttling enabledSeverity
annoyance