-
-
Notifications
You must be signed in to change notification settings - Fork 716
Eek! Performance Problems
This document is out of date.
A more modern version can be found within the repo
My experience is that performance problems arise because either:
- you are using
debug
middleware and it is doing deep, CPU intensive diffs. Remember to removedebug
in production using this technique - large and complex objects are being given as props to components
Point 2 can be a problem because Reagent uses =
to compare the previous value of a prop with the new value of that prop, when determining if the component needs rerendering. A deeper discussion is here.
If those props are big data structures which differ only in some tiny, leaf aspect, then a lot of CPU cycles will be spent doing the =
comparison only to eventually work out that, indeed, the answer is false
.
This problem is exacerbated when components return a lot of hiccup, because lots of hiccup normally means lots of props which, in turn, means lots of =
work to do on each of those props. Any rerender with those characteristics could end up chewing a lot of CPU cycles.
Imagine you were rendering a 19 by 19 "Go" board.
And imagine that you have a high level board renderer component which creates hiccup for the 361 sub components (19 x 19 grid), and that it provides 3 props to each child :
- grid x cord
- grid y coord
- a chunk of data representing the current game state, from which each of the 361 individual grid components is expected to extract the data they need to render their grid position.
This arrangement could be slow.
First, you have a parent component returning hiccup for 361 sub-components and that's a lot of hiccup!! Sure, it might not be much code - just a couple of nested for
, but the hiccup data structure built will be substantial.
Second, after the board renderer returns all this hiccup, for every one of those 361 sub-components, Reagent must then check the 3 props to see if they are =
to the value last rendered (to determine if they, in turn, need to be rerendered), and the comparison on the 3rd prop (game state) might be deep and expensive. Worse, we do the same expensive check 361 times in a row, and every time we get a false
(because games state is not =
to last time).
Third, because Reagent gets 361 falses
, it will further rerender all 361 sub-components even though 360 of them produce the same hiccup as last time - only one position in the gird has changed.
So, when a new stone is placed on the board, and the game state changes, that triggers a large amount of unnecessary calculation, just to figure out that there's only a rendering change at one point in the 19x19 grid.
So, that's how you can get a performance problem: lots of hiccup, mixed with time consuming =
tests on big props.
The solution is to not do the unnecessary work. Duh!
Produce only the hiccup that is needed. Don't unnecessarily pass around big complicated state in props, unless you really need to.
In the Go example described above, for each new stone placed, only one point in the Go board actually needs to be rerendered, and yet our code asked Reagent to chew a lot of CPU to figure that out.
These kinds of tweaks would improve performance:
- don't give the entire game state to each of the 361 sub components and then ask them to extract what they need. Instead, give each just the state it needs, and nothing more. That will make the
=
process faster. It will also allow for Reagent to figure out that 360 of the sub components have the same props as last time, and don't need rerendering. And, so, only one sub-component will be rerendered when the parent "board level" component rerenders. - Also, could you render the board row by row? So that less hiccup is produced by any one component? Can those rows
subscribe
to just the data for their row, so they only rerender when the row-data changes; they only generate hiccup when something really has changed?
Of course, the way to really track down what is going on is to use the OFFICIAL debugging technique. See the four dominoes play out in the console. You may be surprised by what you find is happening.
Be aware that tracing adds its own performance drag - there's the overhead of all that stuff getting written on the js console. Especially if the data getting traced is big - for example, tracing all of app-db
in the console can take a while and force Chrome devtools to take masses of RAM. So you may want to selectively add tracing when poking about.
Deprecated Tutorials:
Reagent: