-
Notifications
You must be signed in to change notification settings - Fork 0
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
Fix performance issues likely caused by unnecessary renders #50
Comments
Hey @bvaughn, I have some questions related to the perf improvements:
By the way, we've just been told that the MLH has scheduled a hackathon for the fellowship next week, so we likely won't be working on the profiler until the 20th. |
I believe the only restrictions would have to do with licensing. If it's a license like MIT then we should be fine. If it's a more restrictive license, or a pay-to-use license, then we would not be able to use it. If you have a particular package in mind I could take a look?
Interesting. I have 0 experience with WebGL. I thought it was mostly for 3d. Can browser extensions run WebGL? I assume so but have no idea?
Yeah I should be able to do this in a day or so. 😄 Remind me if I forget.
By "elements" you mean... data points we render onto a canvas? I guess the primary trick is: Don't do it 😅 I'd probably have to spend some time profiling and thinking about this. If the main problem is that we're hogging the UI thread iterating over tons of data, maybe there's a way we could front load some preprocessing to make our rendering more efficient. Are we slowest when we're zoomed in? If so, maybe we could split the data into chunks somehow so we wouldn't have to iterate over e.g. all events but only chunks that cover the time range we're viewing. Are we the slowest when we're zoomed out? Maybe we could preprocess into different zoom levels, and just filter out anything that would be too small to show at a certain level anyway (so we don't waste time iterating over it each time). In other words, if the slowness is coming from our JS iterations- maybe we can reduce the work we're doing there. (In that case, I don't think WebGL would help us any.) If it's coming from the Canvas itself, then maybe we could find a way to render
Thanks for the heads up! |
Great! I haven't looked in detail into any packages just yet, but if I'll want to use one that's not using the MIT license I'll let you know.
Me too, but I have a hunch it'll be faster. I'll read up further and make a proof of concept if it looks promising.
Great question! I didn't think of this. I'll check this first before looking any further; it'll be a real bummer if it isn't supported.
Oh I didn't clarify this, sorry. This question was in the context of my ReactART poc that I was working on today. I hope I'm using the terms correctly, but essentially I was trying to turn your Anyway, I think your response was in the context of the existing profiler code. The current profiler is slowest when zoomed out, because there are many more things to render. Here's a screenshot of a profile of our profiler: It looks to me like most of the time is spent calling canvas context methods, which is why I'm thinking it may be a good idea to see if WebGL will be a good alternative. The GPU can also tends to spend a really long time processing after a render, but I haven't looked into it. I'm also not sure if WebGL will help with this: |
I wouldn't use ReactART for this. Too much overhead from creating all fo the React components. This is an area where using an imperative escape hatch (e.g. Canvas) is probably the only real solution |
Gotcha. Maybe we can reduce the number of times we call Canvas methods somehow. |
This commit begins a stack of PRs that optimizes our flamechart. Broadly, we need to replace the use of Speedscope's flamechart types with our own types that are optimized for our rendering code. As flamechart was always moved around together with ReactProfilerData, it makes sense to move flamechart into it. That also necessitates moving preprocessFlamechart into preprocessData. This PR works towards #50. * `yarn flow`: errors present, but no errors in affected code. * `yarn lint` * `yarn start`
… frame type Lays groundwork for #75 by reducing the flamechart data passed around the app. Was also intended to help #50 by moving some division operations from the hot render loops into preprocessFlamechart, but any performance gains are negligible. * `yarn flow`: no errors in modified code * `yarn lint` * `yarn start`: no performance difference
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile
This commit begins a stack of PRs that optimizes our flamechart. Broadly, we need to replace the use of Speedscope's flamechart types with our own types that are optimized for our rendering code. As flamechart was always moved around together with ReactProfilerData, it makes sense to move flamechart into it. That also necessitates moving preprocessFlamechart into preprocessData. This PR works towards #50. * `yarn flow`: errors present, but no errors in affected code. * `yarn lint` * `yarn start`
… frame type Lays groundwork for #75 by reducing the flamechart data passed around the app. Was also intended to help #50 by moving some division operations from the hot render loops into preprocessFlamechart, but any performance gains are negligible. * `yarn flow`: no errors in modified code * `yarn lint` * `yarn start`: no performance difference
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile
… frame type Lays groundwork for #75 by reducing the flamechart data passed around the app. Was also intended to help #50 by moving some division operations from the hot render loops into preprocessFlamechart, but any performance gains are negligible. * `yarn flow`: no errors in modified code * `yarn lint` * `yarn start`: no performance difference
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile
… frame type Lays groundwork for #75 by reducing the flamechart data passed around the app. Was also intended to help #50 by moving some division operations from the hot render loops into preprocessFlamechart, but any performance gains are negligible. * `yarn flow`: no errors in modified code * `yarn lint` * `yarn start`: no performance difference
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Also adds a new `ColorView` view that fills the flamechart area with a solid background color to fix these issues: 1. Black empty areas appearing below flamecharts without much stack depth 2. Lines appearing between flamechart rows. My hypothesis is that these are appearing due to subpixel rendering, even though the flamechart rows' `visibleArea`s are all integers. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile * `yarn test`
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Also adds a new `ColorView` view that fills the flamechart area with a solid background color to fix these issues: 1. Black empty areas appearing below flamecharts without much stack depth 2. Lines appearing between flamechart rows. My hypothesis is that these are appearing due to subpixel rendering, even though the flamechart rows' `visibleArea`s are all integers. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile * `yarn test`
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Also adds a new `ColorView` view that fills the flamechart area with a solid background color to fix these issues: 1. Black empty areas appearing below flamecharts without much stack depth 2. Lines appearing between flamechart rows. My hypothesis is that these are appearing due to subpixel rendering, even though the flamechart rows' `visibleArea`s are all integers. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile * `yarn test`
Allow the view system to render only affected flamechart rows. This greatly improves flamechart hover performance, which has a high impact on our app's performance as the flamechart is the slowest view to render. Also adds a new `ColorView` view that fills the flamechart area with a solid background color to fix these issues: 1. Black empty areas appearing below flamecharts without much stack depth 2. Lines appearing between flamechart rows. My hypothesis is that these are appearing due to subpixel rendering, even though the flamechart rows' `visibleArea`s are all integers. Related to #50. * `yarn flow`: no errors in changed code * `yarn lint` * `yarn start`: 60 FPS hovers on our Facebook.com profile * `yarn test`
This issue has become a bit of a catch all issue for all performance issues in the app, but I think we've gotten perf to a pretty good level. 🎉 I'll close this issue and we can open more specific ones in the React repo if we have to |
Steps
Main user actions that cause renders
Cursor traversal over empty (i.e. no React data or flamegraph node) area
Cursor hover over React data or flamegraph node
Currently, we're repainting the entire canvas, when the only thing that changes is the highlighted data/node.
Potential optimizations
Use a second canvas, layered on top of the existing one, that only displays hovered data/nodes. Recommended by https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas#Use_multiple_layered_canvases_for_complex_scenes. We need to see if there'll be any syncing issues if the canvas is scrolled when an item is hovered over.
Implement a rudimentary layout system that allows us to determine an area of the canvas to redraw, instead of redrawing the entire canvas. This will likely not be enough for larger profiles as the cost of rendering an entire section is pretty high. However, this will allow us to formalize and expand the scope of the rudimentary section stacking that we have now.
Scrolling/zooming/resizing/reloading.
A rerender of the entire canvas is necessary, unless there's a way to translate the canvas buffer.
Slow renders will mean jittery scrolling.
Potential optimizations:
Unclear, but all will likely be in the individual render functions. We should look into how Chrome's performance tab is so smooth. (Update: Chrome isn't very smooth when there are many flamegraph events either) I don't think offscreen canvas will help here as almost all the computation is related to rendering the canvas.
Don't render React events when they're not visible.
Wild idea: investigate using WebGL instead of 2D canvas.
Summary of options
UIViews
Update: Implemented in #80.
Implement rudimentary layout system similar to iOS's UIViews/CALayers. This is intended to allow us to determine an area of the canvas to redraw, instead of redrawing the entire canvas.
Requirements:
getHoveredEvent
current does.UIScrollView
if we have horizontal layouts but that sounds a bit overkill right now.Pros:
Cons:
Questions:
position: sticky
and scrolling, so it'll be great if we could just use the browser to do the actual layouts for us, possibly in invisible divs.ReactART
A canvas renderer that's in the React repo.
Pros:
Cons:
Questions:
Things tried:
requestAnimationFrame
instead of the existingsetTimeout
, drastically reducing the delay between React's commit phase and the commits being rendered onscreen (unlike in React DOM, ReactART's commit phase does not draw directly to the canvas; the Art library does it after or schedules another rendering task for that).Rectangle
was very slow as it drew rects using lines instead of calling the canvas context'srect
method. As we are drawing a large number of rectangles, this was a significant bottleneck. I added a way to call the canvas context'srect
to Art and ReactART:Path.rect
function that calls the canvas context'srect
functionRectangle
class to callPath.rect
if the radii are 0..map
.React.memo
on our components, especiallyFlamechartNode
, to reduce unnecessary work during the reconciliation phase.Resolution: Likely too slow to be suitable.
The API is very nice, and the hit testing and event handling are really convenient.
However, both React and ReactART are too slow, as we have too many items to render. This screenshot below shows a mouseover over a flamechart node in a big-ish profile with a total of 121813 nodes generated by clicking around my toy concurrent demo app. As there are too many React elements even in this toy profile, the reconciler takes a long time to do its work, even after
React.memo
has skipped the rendering of most elements. Additionally, when more nodes are visible, Art's canvas rendering code also takes a long time, seemingly due to itssetTransform
calls.WebGL
Investigated Pixi.js and Two.js, but both are not performant enough to handle tens of thousands of rects every frame.
Optimize hot loop code
Replacing the division operation in this line with a constant reduced the runtime of
renderFlamegraph
by a surprising amount (eyeballed the flamegraph: it looks like that reduced the runtime by >60%). Looks like some work can be done here to optimize all the hot loops:The text was updated successfully, but these errors were encountered: