DataGrid - Scrolling performance #9001
Replies: 6 comments 13 replies
-
These points make sense to me 👍. I didn't dive deep. To measure success, I think that we need to measure two things:
In the very first implementation of the data grid, we used a trick that would not scroll the rows during very fast scroll events, to guarantee 2. We have reverted this behavior since https://mui.com/x/react-data-grid/#commercial-version: Screen.Recording.2023-05-15.at.23.15.25.movAG Grid is taking an opposite UX tradeoff, while we debounce the rendering (at least it's how it feels, we abord rendering if another update tells us to stop), it feels like they throttle, what's displayed is wrong (feels like it's lagging), but not white areas: Screen.Recording.2023-05-15.at.23.24.19.movhttps://ag-grid.com/example/ You can most feel the throttle behavior around the end, it takes the grid one or two rerenders to display the final correct rows. I think that the direction they took could maybe make sense (no sure). To pull this off, I suspect that they keep the current rows in their current viewport position for the next pain until the new rows are rendered, they might also predict the future scroll position based on speed (with a low pass filter http://phrogz.net/js/framerate-independent-low-pass-filter.html). I mean, see this recording, how is it possible that the same rows are visible for different scroll positions: Screen.Recording.2023-05-15.at.23.29.11.movSo overall, what we could measure with 2. is the speed at which end-users can scan the results without having to wait for the paint: Screen.Recording.2023-05-15.at.23.33.41.mov |
Beta Was this translation helpful? Give feedback.
-
I wonder what would these changes mean for the
|
Beta Was this translation helpful? Give feedback.
-
I think we can drop this plugin: Lines 142 to 143 in 52330d2 We clearly don't support IE 11 anymore (we started using |
Beta Was this translation helpful? Give feedback.
-
How did you measure the performance? Inside the
I don't change my machine since 2021. Among the features we released something impacted the scrolling performance. |
Beta Was this translation helpful? Give feedback.
-
The issue with the styling engine is because emotion serializes all styles to derive a key for caching, however, it does this every time the component is rendered. Making cells and rows a |
Beta Was this translation helpful? Give feedback.
-
Hey, regarding scrolling performance, wouldn't it be possible to make column virtualization available also for rows if their height is set to auto ? |
Beta Was this translation helpful? Give feedback.
-
This is a summary of the observations I've made regarding scrolling performance. The performance profiles were done with a datagrid filled with either
<button>
elements or MUI<Button>
components, on an Intel Core i7 10th gen CPU, and are there to give an approximate idea of the CPU costs. They all show onescroll
event call.1. Re-renders
This is the biggest cost by far. Currently, we
React.memo
the cells but not the rows, which means that on every scroll event all our rows are re-rendered. Below is a flamegraph where theGridRow
render calls are highlighted. It is very apparent that we're doing more work than we need to do, we must therefore focus on avoiding it.Adopting
React.memo
: issues & solutionsIt is currently not straightforward to adopt
React.memo
because our internal API state uses an imperative mutable state that lives inside of a React context, but outside of React's reach. From React's POV, the API handle never changes. The reason why the datagrid currently works despite the external state is that we re-render more than we need to, which gives React a chance to read the API state often enough. To make theReact.memo
optimization work, we must therefore make our API state accessible to React. To do so, the plan is to use a Redux-like store within React, whose core logic is demonstrated here. In a few words, when the API state changes, it will publish an update to its listeners. The listeners will conveniently be created byuseGridSelector(...)
, which will also be storing the selected value in aReact.useState
, which will allow React to update components granularly. This architectural change will also unlock improvements in many other use cases, for example it would become reasonable to have a cell focus change event that would only re-render the 2 cells that changed focus.We could also apply the same pattern to the grid root props, where components could select a slice of the props and update based on it. Currently, whenever the root props change, any component that uses them needs to re-render, regardless if the props they're actually using have changed.
Plan
I plan to start working on these changes as soon as possible, unless there are objections.
NOTE: The next points are presented for completeness' sake, but are by far less important than the first one above.
2.
_objectSpread2
Babel's
_objectSpread2
is what{ ...object }
desugars to. Unfortunately, the ECMA spec defines the spread operator in terms ofObject.defineProperty
, which means it is nearly an order of magnitude slower thanObject.assign
. And our codebase is littered with it.Possible improvement
While there are subtle differences between
{ ...o }
andObject.assign({}, o)
, it might be worth considering adding a babel transform to turn our spread operators intoObject.assign
.Plan
I don't plan to implement this change unless there is positive feedback from the team.
3. MUI Core components & styling
While not technically related to the datagrid, I'll point out that using MUI components is very expensive. The biggest cost they have is by far the styling engine. The report below represent one scroll event callback with a datagrid filled with
<Button>
components.Plan
I'll discuss with the core team, they are investigating griffel and we should also switch to it if/when they make the switch in core.
Beta Was this translation helpful? Give feedback.
All reactions