Thoughts on the overall approach with React compiler #77
Unanswered
shudv
asked this question in
General Questions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
I’ve been trying to understand React Compiler and I want to share a perspective based on actual performance issues I hit in a React app — and a small experiment I built afterwards.
I want to clarify up front:
I don’t have deep expertise with React Compiler internals. My understanding is based on the docs and examples published so far. If I’m wrong about how it works, I’m happy to be corrected.
Here is my current understaning-
The react compiler is trying to "auto-memoize" derived data and callbacks so that the references to sub-trees in elements are stable and render passes can be made lighter as a result resulting in - 1. better performance and 2. cleaner user-land code.
And this is where the problem starts.
Memoization has 2 fundamental problems-
First up - here is a tiny vanilla JS example that shows that memoization as a default is not always good for performance-
The memoized version is slower, even though the computation is trivial.
Secondly, memoization benefit entirely depends on R/W ratios - if invalidations/wriets are nearly as frequent as reads, memoization is almost guaranteed to a net-negative. If R/W >> 1, then the added complexity of memoization might pay off.
My concern is simply this - it's hard for a framework to estimate the R/W characteristic of an app, so broad memoization might be an incorrect default. As an example I might be doing 100 potential computations in my render loop out of which 1 might be a good memoization candidate. In this case, it's better to let the memoization code reside in user-land as compared to the React compiler applying a broad memoization on all 100 computations.
What should we do instead?
With whatever experience I have with React, I think React diffing is already quite surprisingly fast in practice, even though it is counterintuitive to my initial intuitions (I spent a lot of time trying to find evidence that Svelte might be faster for my use case but I couldn't). The real performance problems were almost always because of a bad render code and not extra render passes.
Overtime I got more and more convinced that the right way to write React code was to not have
useMemo,useCallbackanduseEffect's at all (In fact, in our repo we have been able to completely get rid of the usage of these without any noticeable performance hit). Then profile your app to detect performance hotspots and have targetted fixes for those. This is not a framework-land problem and this is a userland problem to solve.So infact I find myself aligning with the stated goal of React compiler, which is to get rid of these hooks from userland, but the approach differs. In my experience, these hooks aren't supposed to be used in the first place as frequently as they are in practice.
My request from this community
I would love to see real world performance benchmarks with React compiler and any quantitative evidence that we have that this is indeed a net-positive for the ecosystem. In fact, I would love to volunteer to try this out for the repository that I work on in my company which has about 200k lines of React code if not more.
Closing thoughts - memoization is tricky
I did come across a class of problems where I felt the need for memoization and that was for large keyed-collections. I built a tiny library (1.4KB min+gz) called memotable to encapsulate that abstraciton and while working on it I realized how tricky it is to get memoization right. In fact, I almost didn't do it because I was doubtful about whether patterns like these have any real-world user facing impact. Yes, you might save up on some CPU cycles, but is it really worth it? That's the question.
Beta Was this translation helpful? Give feedback.
All reactions