-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
RTK with Immer x100 slower than vanilla Redux at average (up to x400) #4793
Comments
Hmm. This is admittedly slower than I would expect - I would expect an "add item" immer reducer here to take more around 2ms than 6ms. This might be worth looking into. That said, the 100x/400x from your headline here are highly artificial and hopefully never occur in a real application, and that also goes into the whole benchmark a bit: See the style guide in these points: The idea here is that an action should represent an event happening in your application - in almost all situations, those are either user-induced events (clicks), or things like the start or return of a network request. Of course there are exceptions to this - what comes to mind are cases where we've seen Chatbots processing messsages from hundreds of users, or financial dashboards showing real-time data coming in via websocket. These cases can be optimized, or in a single reducer you might actually choose to opt out of immer by just writing a reducer by hand - but these cases make up the 1% of poweruser edge cases, not the 90-95% of users we primarily target and for whom we want to avoid actual real-life bugs. That said - I already started with this: these numbers feel too high by a factor of 3-5 to what I'd expect, and it is worth investigating. Could you maybe adjust the benchmarks to resemble real-life application usage a bit more?
|
About actions per second - I was once participated in a project that had around 100 actions per second - it was a fork of opensource mobile chat app Mattermost, they recently switched to watermelon db (bad solution). Also many projects got lots of actions per second on app launch when fetching lots of stuff. Also, we need to keep in mind that there are mobile devices, and even more, android mobile devices (x10 slower than iOS), and even more old android mobile devices with low battery, so for a good app there should be a pretty big performance reserve for such cases, if comparing to desktop. And actually redux does pretty well in terms of performance. I would say much better than I expected. So it has a great potential in performance, but looks like not with immer. It adds a huge limitations here, and that chatting app would definitely freeze out if someone decided to use it (they used vanilla redux back then), as it already had performance issues due to some very bad decisions like keeping theme in single redux store and many more. So not using redux when it actually could work very well is not cool honestly. There probably are some situations where there can be better solutions, but it is definitely not a chatting app.
Also did some refactoring etc. EDIT: As for making plain reducers - they don't have all this cool boilerplate of slices, so not sure why not just disable immer for them / for slice reducers. Also, I guess there definitely should be a tip in performance doc about that. |
I don't understand what you're saying with this sentence:
As Lenz said: Redux was never intended to be the fastest lib out there. The primary goal is predictability, and accidental mutations go against that. Sure, I wish Immer was faster :) I can see that the majority of the time in your benchmark is being spent in Immer's I know @mweststrate has spent a lot of time trying to optimize it, but also have it be correct in a variety of update situations. Maybe there's still something that could be done to speed it up, maybe there isn't. Might be worth filing a perf issue over in the Immer repo to discuss this. So yeah, in cases where you're using Redux and perf is still critical, you may feel the need to hand-write those reducers. But overall we still see Immer as the right default choice for RTK. |
And I even agree that enabling immer by default is probably fine. At least removing it by default on current stage can be very confusing. |
Immer has been something we've been unwilling to budge on before (#242) and I don't see that changing soon. We have an open thread/PR for investigating supporting immer alternatives such as mutative (which may perform better), but it's generally been pretty low priority. |
The problem with that is that you get something that looks the same, but actually requires you to write completely different code, or you'll end up with a bug-ridden mess. Honestly, I don't want to enourage that, especially in the age of LLMs that just see "looks similar" and then spit out code they've seen a million times. I'm fine with exploring alternatives to immer, but I am very hesitant on going without an immutability helper. Code that needs to be written in immutable style should look significantly different than code that can be mutable.
Even for an experienced dev, this is a mental hurdle - switching between projects where one requires immutable code and the other one doesn't, it's easy to accidentally use the wrong paradigm if it's not obvious what is required. Neither an option nor a method name helps here - the case reducer object can easily become mutliple screens in size, so you won't be aware what method name you used, or what option you set somewhere. Honestly, in those cases I'd very much prefer if people used |
And yes, sometimes immutable code really looks ugly when it is complex, while it is pretty simple for simple cases. When immer appeared, a good alternative was using immer for complex cases, while keeping immutable approach as basic. And I believe everyone who uses redux must know the basic concept and must be able to write immutable code.
That being said, I strongly believe that:
EDIT: This problem may also significantly decrease the popularity of redux. Devs often like performance even more than it really matters - and you can't do anything about it. It is a risk that no-one wants to have in their project. |
I wholeheartedly disagree here - it's a massive bonus that we don't force people to learn both of these approaches. They can learn them once they need them.
This is an Open Source project run on the weekends by 2-3 volunteer maintainers. Experiments like this are welcome, but they can also exist outside of RTK - I don't think we have the bandwidth to implement or maintain something like that.
Again, this is outside the scope of what we can do. This could easily live outside of RTK and be it's own thing.
You are dismissing one important point here: Redux Toolkit is the recommended default way of writing Redux since 2019. That's over 5 years, and more of half the time Redux exists. In that timeframe, RTK had about 430 million downloads. This did not significantly reduce the popularity of Redux - since in most cases, it doesn't present itself at all. Yes, this is something that needs investigating, but please stop overplaying this like "RTK is 400x slower for everyone". Your use case is an extreme outlier, and for most people the slowdown is insignificant. We are in the unique situation where we have to strike a balance between "perfect performance for everyone, but dangerous footguns" and "perfectly viable performance for 90-95% of use cases, but no footguns". I do not want to introduce an option that adds a significant speed improvement for a dozen apps while making it easier to introduce bugs in tens of thousands of apps. |
Yeah. To be clear: We do care about performance. We want Redux, RTK, and React-Redux to be as fast as reasonably possible. I personally have put in a lot of effort over the years working on perf optimizations. Upgrading to Immer 10 to get its perf improvements was one of the big goals in getting RTK 2.0 out the door. That said, we also have to balance that with other priorities as well. We also care greatly about correctness. As I said above, accidental mutations were historically a consistently bad problem. I lost count of how many times I answered people's questions about "why isn't my UI updating?" and "why is the logic not working right?" that boiled down to "oops, I accidentally called We also care about DX. "Boilerplate" was the top complaint about Redux usage, and hand-written immutable update logic was one of the major reasons. Immer fixed that too. Finally, reducer perf is not the sole aspect of perf that matters. From everything I've seen over the years, reducers are very rarely the main perf bottleneck. It's typically the cost of updating the UI that takes longer, especially as many components re-render. Along with that, as Lenz said, most Redux apps are not dispatching hundreds of actions a second, and writing hand-written reducers is still an entirely valid option if better perf is necessary. (Or, as he also suggested, copy-paste If I could wave a magic wand and make Immer have 0 overhead, I'd do that. If I could wave a magic wand and make it trivial to swap Immer for another lib or turn it off and still use If you look at #3074 , we've experimented with trying to make the use of Immer configurable, which would also in theory allow swapping it with a no-op set of update functions too. But, that PR already got really complicated, because we do rely on Immer's functionality everywhere. So, it's not a trivial effort to do that swap. (And frankly we've already got a huge proliferation of "builder callbacks" and Given all those factors, we've chosen the tradeoff of defaulting to better DX and better runtime safety, at the expense of some amount of reducer perf. That provides the most benefit to the largest number of users. (And, somewhat conveniently, also reduces the amount of time we as maintainers have to spend answering people's questions about why their code doesn't work as expected.) So, yes, I'm always interested in ways that we can improve Redux-related perf. But we have to balance all these factors for the real world :) Honestly, the best thing that could be done here is to investigate Immer's internals and try to understand why |
I've wrote some benchmarks trying to understand how Immer influences the redux performance and got some very strange results:
original
doesn't make much difference.There are probably some mistakes in this code (initially generated by AI), so appreciate some help to find that out.
Here is the code.
To run:
NODE_ENV=production node --expose-gc benchmark.mjs
Environment:
Results:
Data, ms (the lower the better)
Data, x times slower (the lower the better)
Chart, x times slower
PS.
configureStore
andcreateSlice
. Vanilla means usingcreateStore
fromredux
.The text was updated successfully, but these errors were encountered: