Skip to content
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

Precise Activation RFC #207

Open
RobPruzan opened this issue Jan 27, 2025 · 7 comments
Open

Precise Activation RFC #207

RobPruzan opened this issue Jan 27, 2025 · 7 comments

Comments

@RobPruzan
Copy link
Collaborator

RobPruzan commented Jan 27, 2025

Precise Activation RFC

Table of Contents

One large (and mostly correct) criticism of react scan is it can be very noisy for a large set of problems. Re-renders are not always expensive, and re-renders are not always the problem.

To resolve this, this RFC proposes to:

  • Only show core outline visualizations when we detect slow renders
  • Provide a direct summary of what caused slowdown in toolbar
  • A new set of visualizations applied to the dom which provide a summary of what happened

Only show core outline visualizations when we detect slow renders

React scan can be extremely noisy when there are no problems. For example, scrolling on twitter causes large re-renders, but no drops in performance. This leads to a noisy-first experience that forces the user to turn off react scan till they explicitly have a problem.

Screen-2025-01-27-002837.1.mp4

To transform react-scan to an "on first" experience, we can only show react-scan outlines when the user actually wants to see react-scan outlines. This can be formalized - only show react-scan outlines when re-renders meaningfully slowed down the app.

Meaningfully is initially defined as:

  • 200ms interaction times

  • 50ms task times

With the use of bippy instrumentation, fiber timings, and browser API's, we can directly calculate:

  • How long a task took
  • The percentage of the task taken up by react renders

This means we can precisely trigger react-scan overlays when component renders meaningfully slowed down the app.

Note: We will still provide an option to always have react-scan always on.

Provide a direct summary of what caused slowdown in toolbar

While ephemeral outlines when the app is slow is an improved default, it is still not sufficient information to make any decision. The user will still have to manually find the problem (perhaps through the react profiler), find the re-rendering components, optimize them, check back on react-scan to see if anything changed.

This is a suboptimal debug flow:

  • It requires an external tool to completely understand the problem
  • The external tools publicly available are either too hard to understand (react devtools, chrome profiler) or too slow to use (console logs, performance.now)

React-scan internals already know a significant amount of information about where the problem lies.

This RFC proposes to leverage the react-scan toolbar's dynamic UI to create a notification to show a slow problem was detected.

Image

Clicking this notification would reveal a UI containing beginner-consumable information about performance problems.

Image

This toolbar UI will leverage the runtime powering react-scan replay to collect information about the problem https://react-scan.com/replay

Image

Note: The UI for presenting this data on the toolbar is still undefined

The data we have access to (roughly ordered by predicted usefulness) is:

  • Component render times
  • % offscreen renders
  • Forced reflows
  • Components that could have been memoized
    • Useful to verify react compiler impact
    • Useful for the large set of users that can't move to react compiler (organizational reasons)
  • Components with unstable props, state, or subscribed context
    • Unstable is defined as the reference of the javascript object changed
  • Components with props/state/subscribed context that had unnecessary references changes
    • Unnecessary reference changes is defined as if the object is structurally comparable to the previous object, and its equal
  • Slow CSS properties used
  • Slow CSS mutations
  • Unnecessary renders (a render is defined as unnecessary if it rendered with no consequence to the dom)

We also have data that can help verify performance optimizations:

  • All the components that were successfully memoized and avoided rendering during the slow interaction/long task

A new set of visualizations applied to the dom which provide a summary of what happened

The approach that worked very well for react-scan is visually overlying UI over a fibers stateNode when it re-renders. Formally, we visualized performance problems in a very digestible way.

It is not the case showing re-render outlines is the only useful visualization that exists for performance problems.

The set of visualizations we can do in the current react-scan model (always on) is limited. Outlines must deliver the information they represent semantically in <1000ms (roughly, the time it takes for outlines to fade out).

There is a hard ceiling of the maximum amount of information we can represent in <1000ms (each visualization takes time to process, so stacking visualization stacks like debuffs in a video game).

In the new react-scan model, we will provide digestible summaries the user can understand through the toolbar. The user must manually decide to view the summary, which means the user can review the summary for any period of time (our new ceiling is ∞ ms).

Because of this, the universe of visualizations we can perform at this time significantly increases.

For example, we can trivially visualize slow component renders with a heatmap based on component render times during the slow interaction or long task:

(A dummy example of a component render time heatmap on the react-scan website)

Image

TLDR

The features laid out in this RFC can enable react-scan to become a "digestible always on profiler" to allow users with no performance experience to quickly find performance problems

@aidenybai
Copy link
Owner

nice @RobPruzan, this accurately describes the problem very well

one note: you propose 100ms tasks but long task classification is >50ms. i think we can follow this standard. we should also detect repeated tasks that are >16ms somehow (potentially thru FPS)

@RobPruzan
Copy link
Collaborator Author

RobPruzan commented Jan 27, 2025

nice @RobPruzan, this accurately describes the problem very well

one note: you propose 100ms tasks but long task classification is >50ms. i think we can follow this standard. we should also detect repeated tasks that are >16ms somehow (potentially thru FPS)

Yeah, I (arbitrarily) picked 100ms since 50ms felt like it might activate react-scan too frequently, but agreed should stick to standard. If it becomes annoying, we can allow user to tweak defaults through api

@aidenybai aidenybai pinned this issue Jan 27, 2025
@lxsmnsyc
Copy link
Collaborator

at least >16ms should be the optimized blocking time, but the max running time can be 400ms (Doherty Threshold). Not sure if 100ms is "justifiable" hmmm

@tigerabrodi
Copy link

tigerabrodi commented Jan 27, 2025

Yo, super exciting stuff

The auto-activation based on actual performance issues is a really nice one. The current always-on overlays is annoying ill have to admit lol. 👍

one thought that came to mind:

When detecting slowdowns, might be worth looking at a set of small frame drops too?

even if the drop isn't huge, it can still feel janky, imagine:

Frame 1: 17ms (drop)
Frame 2: 17ms (drop)
Frame 3: 17ms (drop)
Frame 4: 17ms (drop)

isn't not huge but still not the best experience when it happens like that

if its something like

Frame 1: 17ms (tiny drop)
Frame 2: 16ms (good)
Frame 3: 17ms (tiny drop)
Frame 4: 16ms (good)

i don't think the user would notice it

i dont know a good number for how many small frame drops in order to actually classify it as a bad experience, could be something to experiment with?


ahhh

i see @aidenybai already mentioned this (same thing?)

we should also detect repeated tasks that are >16ms somehow (potentially thru FPS)

@kurtextrem
Copy link

fwiw, the 200ms "interaction time" hints to INP I assume, however, 200ms was only picked because hitting 100 ms on mobile for p75 of the web is an almost impossible task. The goal should be 100 ms, however.

See also: w3c/event-timing#118 (comment) and https://web.dev/articles/defining-core-web-vitals-thresholds#inp-quality. So 100ms sounds reasonable.

I think something like an option for "target FPS" would make sense (for the granular renders, e.g. if you target 120 fps, 16ms renders are too slow already), and target "interaction to next paint", where you can pick between 50ms, 100ms and 200ms.

@pivanov
Copy link
Collaborator

pivanov commented Jan 27, 2025

Nice one!!! 🎯

@RobPruzan
Copy link
Collaborator Author

RobPruzan commented Jan 28, 2025

fwiw, the 200ms "interaction time" hints to INP I assume, however, 200ms was only picked because hitting 100 ms on mobile for p75 of the web is an almost impossible task. The goal should be 100 ms, however.

See also: w3c/event-timing#118 (comment) and https://web.dev/articles/defining-core-web-vitals-thresholds#inp-quality. So 100ms sounds reasonable.

I think something like an option for "target FPS" would make sense (for the granular renders, e.g. if you target 120 fps, 16ms renders are too slow already), and target "interaction to next paint", where you can pick between 50ms, 100ms and 200ms.

Yeah good points, a bit of nuance to pick correct defaults.

Another thing to consider is we are mainly concerned with the time it took to re-render components, not necessarily the whole interaction. Many cases large interaction times are caused by a lot of components on the screen rendering, meaning lots of dom elements. So, a reasonable expectation is timings of the interactions may breakdown as:

  • 100ms component re-renders
  • 10ms javascript event handlers
  • 20ms raf's, observers
  • 50ms style recalcs, paint, commit, compositing/drawing to screen

The task being blocked by 100ms of component renders is probably a reasonable default to show outlines (100ms may still be too high)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants