Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance of accessing deeply reactive state #11851

Closed
peterkogo opened this issue May 31, 2024 · 10 comments
Closed

Performance of accessing deeply reactive state #11851

peterkogo opened this issue May 31, 2024 · 10 comments

Comments

@peterkogo
Copy link

peterkogo commented May 31, 2024

Describe the bug

Context

Our libraries state is an array of objects. The deepest nesting level is maybe another object in each object.
Having array sizes of thousands of elements is not unheard of and has been completely manageable performance-wise in Svelte 4.

Issue:

Because this array is representing state I converted what was previously a store into a deeply reactive state.
I am observing quite a heavy performance impact. The surprising part is that this is not only limited to the rendering of components based on that state, but also affects simple access of the array in a non-stateful context.

For reference, iterating over an array with 1000 objects of type { position: { x: number, y: number } } yields quite different results - deeply reactive state however, is very far off.

Here are the results of a simple benchmark comparing $state(array) to $state.frozen(array) and to simply accessing the array itself. The function used for taking the measurements is a simple summation (see REPL below for full code).

let sum = 0;
for (const elem of array) {
  sum += elem.position.x + elem.position.y;
}
+--------------+---------+--------------------+--------+---------+
| Task Name    | ops/sec | Average Time (ns)  | Margin | Samples |
+--------------+---------+--------------------+--------+---------+
| baseline     | 465,124 | 2149.9602650215506 | ±6.13% | 46559   |
| frozen state | 161,428 | 6194.690263273545  | ±6.10% | 16159   |
| deep state   | 5,890   | 169779.2869269949  | ±3.03% | 589     |
+--------------+---------+--------------------+--------+---------+

Is this expected, or am I doing something wrong here? I test my code with production builds and can observe similar behavior, but am I accidentally benchmarking dev mode in the REPL?

Can I bail out of the runtime reactivity code path when I simply want to access state programmatically in an unstateful manner?

Reproduction

REPL with benchmark. Check performance tab when hitting change stuff to observe rendering performance.

Logs

No response

System Info

svelte@5.0.0-next.144

Severity

annoyance

@paoloricciuti
Copy link
Member

Can I bail out of the runtime reactivity code path when I simply want to access state programmatically in an unstateful manner?

The easiest solution i can find is create a derived of $state.snapshot and access that...this way will always be in sync with the actual state without the perf baggage of a proxy that create a signal.

That said i wonder if this could be done automatically @trueadm ...would it be possible to avoid the signal creation if not inside an effect? Will it be too late if later on the signal is accessed again?

@trueadm
Copy link
Contributor

trueadm commented May 31, 2024

This PR should fix the performance issues with $state.frozen: #11852.

@peterkogo
Copy link
Author

peterkogo commented May 31, 2024

@paoloricciuti good idea, I will try it out!
@trueadm great! Will update the benchmark once its released.

Just to be clear, $state.frozen actually had an acceptable performance impact but $state is close to unusable in this case.

@trueadm
Copy link
Contributor

trueadm commented May 31, 2024

@peterkogo You're frequently updating a thousand elements at a time. With proxies that is always going to have an impact – there's no way to avoid it. $state.frozen is a far better fit for bare-metal performance cases like this.

@peterkogo
Copy link
Author

peterkogo commented May 31, 2024

But the benchmark is not even updating anything just iterating over it. Though I guess the same answer applies.

@peterkogo
Copy link
Author

@paoloricciuti the idea of using $state.snapshot is unfortunately the slowest. REPL

@paoloricciuti
Copy link
Member

@paoloricciuti the idea of using $state.snapshot is unfortunately the slowest. REPL

That's not what I meant...the benchmark is calling the function over and over again hence is snapshotting much more than needed. You want to keep the snapshot in sync with the data with a derived and accessing that in the function like this

@peterkogo
Copy link
Author

@paoloricciuti fair enough, but you have now basically taken the time it takes to take a snapshot out of the picture - and that is quite a heavy operation on itself. In my specific case we update thousands of positions frame-by-frame, where the time it takes to snapshot a state would also add up to the frame time.

@paoloricciuti
Copy link
Member

@paoloricciuti fair enough, but you have now basically taken the time it takes to take a snapshot out of the picture - and that is quite a heavy operation on itself. In my specific case we update thousands of positions frame-by-frame, where the time it takes to snapshot a state would also add up to the frame time.

Ok so you need to update the data and run through it frame? Maybe it's easier to just keep a separate unstated variable and assign that too?

@peterkogo
Copy link
Author

I don't understand what you are saying. I need to do some calculations on data that might possibly change. "keeping a seperate unstate variable" is the equivalent of doing $state.snapshot when this data changes, and then do the calculations on the non-stateful data. Or am I missing something here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants