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
Gutenberg: Improving Performance on Renders #25780
Comments
That's amazing, @ItsJonQ! Thanks for starting this issue. I wonder why so many things are being re-rendered by just moving the cursor around? I guess there's some internal state that is updated on mouse over, move etc. But is it really necessary? |
@diegohaz Most likely something tracking a selected or hovered block state. That part makes sense. But perphaps... not everything needs to subscribe (or be exposed to) to that bit of state 🙈 |
Block InserterTo get a better sense of performance improvement strategies... I wanted to examine something that was both isolated(ish) and substantial (something bigger than an I think the Block Inserter matches these attributes. 1️⃣ BeforeUpon initial inspection, it looks like every piece of UI (search, individual grid items, icons, etc...) re-render upon 2️⃣ AfterWith a couple of 🏃♂️ Actual performance differences?It already feels zippier. To double check, I ran some performance tests within Chrome. 1️⃣ Before2️⃣ AfterNotice in the after, the frames stay consistent at around 60fps (green) and the computation segments (yellow) are less scary. I can imagine that this particular example (Block Inserter) will progressively degrade as:
My local development is on a well spec'ed MacBook Pro, running MAMP. In other words... these results basically represent a best case scenario. Note: There are probably some CSS-based layout improvements we can do as well. This thread focuses specifically on controlling React re-renders. |
Block Inserter (Update)☝️ continuing from the updates above... I've also optimized search/filter rendering. These updates apply to both the QuickInserter and BlockInserter. Here are the results... 1️⃣ BeforeBlockInserter: Mouseover + searching QuickInserter: Mouseover + searching 2️⃣ AfterBlockInserter: Mouseover + searching QuickInserter: Mouseover + searching 🏃♂️ Actual performance differences?More Chrome performance tests. I performed the following actions for these tests: Mousing over QuickInserter. Launching BlockInserter. Mousing over BlockInserter. 1️⃣ Before2️⃣ AfterBonus: I've included 4x CPU throttled results. There were both... pretty bad. However, the "Before" implementation got absolutely REKT. 1️⃣ Before2️⃣ After |
Block Inserter (Update) Part 2Last series of updates! This is after optimizing hover events some more (and other bits and bobs) The following tests showed me moving my mouse over the first 3 items in the Block Inserter. 1️⃣ BeforeNotice the drop in frames. 2️⃣ AfterMuch smoother :) P.S. Here's the branch if you're curious: The commits need refactoring. I committed just enough to test out the perf. ideas |
Love it! What would it take to get this improvements into a PR we can ship? |
@noahtallen Yay! I can create a PR from the work I submitted in this branch: Before I do, I'd refactor things as my initial attempt was more on the exploratory side. I'm unsure if there would be side-effects from the approach though. That would probably be the biggest challenge. |
makes sense :D I love performance investigations like these :) I'm sure there are lots of aspects of the block editor which could benefit from this. |
Updates!! Some promising results from experiments within G2 Components: |
I prefer to |
@david-szabo97 Totally agree. I believe that all of the components from the base set (
I've only found that problematic a super super low level. As in.. for a particular function that fires often that's part of an HOC used by EVERY component kind of thing. As you mentioned, it's quite easy to identify and remove memoization, thanks to performance/FPS/memory tools from browsers like Chrome :) |
Hi all! For the past week, I've been exploring performance optimizations in the context of UI, Design Tools, and ultimately the Editor itself.
I started sharing some of my findings in the G2 Components project.
This morning, I started looking into Gutenberg to take a peek at how it's currently doing.
(Note: Performance has many many facets and nuances. From time-to-interaction, load times, bundle sizes, DOM paint management, etc... I'm going to be focusing specifically on React re-renders as it relates to state updates)
♻️ (Basically) Everything Rerenders
📹 Video demo of re-rendering
https://d.pr/v/rc6Pud
I did a quick performance test with the React DevTools enabled, specifically the "Highlight updates when components render" option. Just by selecting a block and moving my mouse around (without entering content), we can see the entire editor flashing.
For debugging purposes, I've added some console.logs to the BlockControls component to log every time it re-renders. (It happens a lot).
On my MacBook Pro, things seem to render okay (under real-world use). Albeit, I haven't needed to write long posts or compose something with a mixture of custom/complex block types. I do notice sluggishness when I need to adjust values (quickly) related to layouts, such as column or height values.
To simulate the experience of a less powerful machine (perhaps an older computer or a mobile device), I typically throttle the CPU to 4X slower (via Chrome's performance tools).
That's when you can truly see and feel the pain.
I suspect performance will (exponentially) degrade as blocks (both core and 3rd party) gain richer features, and as the Editor is used in a wider context (e.g. Full Side Editing).
💪We can fix this!
📹 Video demo of improvements
https://d.pr/v/Nq8NDw
In the video clip above, I made a tiny adjustment to the Cover block containing custom BlockControls. You can see that it no longer re-renders (see console.log) as I mouseover the Cover block (yay!)
(This was achieved by using React.memo)
(It's one tiny example. We need to do a whole lot more than that!)
Ultimately, the solution is to be mindful and deliberate with techniques like memoization and state -> prop consumption.
The tricky thing is, these techniques are often meticulous to implement and cumbersome to test. This would probably feel even more difficult for 3rd party block developers, especially for those who don't have a lot of experience with the nuances of React rendering (who can blame them... it's hard, haha).
🛣 A path forward
I think some things we can do would be to:
Component optimization would start at the foundational level (within
@wordpress/components
), trickly upwards to controls and tools seen in areas like@wordpress/block-library
.If you're curious about component library level performance optimization, you can check out examples from G2:
https://github.com/ItsJonQ/g2/tree/master/packages/components/src/Text
It's very intention in managing props, composing hooks, memoization, creating and consuming contexts, and keeping both the React and DOM trees as shallow as possible.
Some of these techniques were borrowed from libraries like Reakit
The best example of #2 in action would be React Redux, which is what WordPress/data is modelled after.
Ignoring the (polarizing) verbosity of the library...
One thing I'd like to highlight would be the mapStateToProps + connect HOC. It's a brilliant mechanic. It abstracts away a lot of the memoization fiddling into a single entry point.
Blocks currently do this as a way to retrieve attributes from WP/Data. That's awesome! However, I feel like more can be after within the Edit portion of the block, especially for blocks with many attributes and controls.
From my experiments, one way to achieve this would be to effectively create a "mini" Redux store within the Block Edit component. Using the same mechanics of only consuming/updating what you need.
🤔 It's going to be interesting
I only started looking into performance within Gutenberg (from a rendering perspective). I acknowledge and applaud the performance-related work that has been achieved by folks before me. These examples can be seen in solutions like WP Data.
It's a solution that excels in data syncing (with WordPress), expansion (block registration), and distribution (passing along values to blocks). One way to describe these features would be "macro" features. Focusing on the "big" stuff.
With a lot of those aspects solved and tested, I think it gives us the ability to focus and refine the "micro" features of data handling. Such as, how attributes can be accessed/updated/rendered in an isolated and high-performant manner. Controlling how micro-interactions (like mouse movements or keyboard updates) propagate render cycles to larger contexts. And systematizing it all, so that all the nitty-gritty aspects of performance handling happens seamlessly (or as seamless as we can make it).
P.S.
🙈 Pre optimization...
There's that famous quote!
Considering the editor can be laggy today... I don't think the ideas I've proposed above are pre optimization for the sake of optimization. We kinda need it now 😅
The text was updated successfully, but these errors were encountered: