-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Rich text: Try debouncing useInput
to improve performance and fix infinite loop
#54819
Conversation
Size Change: +20.6 kB (+1%) Total Size: 1.64 MB
ℹ️ View Unchanged
|
Adding |
The change is also causing e2e test failures. |
Agreed, it's far from ideal as a solution. I haven't been able to find a better one for the time being, but I'm happy to look further. One alternative could be optimizing how we treat batched store updates since it appears that is where the problem comes from. Let me know if you have any ideas in the meantime, @jsnajdr and @Mamaduka |
Another debounce issue is that we must do it every time, even when a post is small or the user's device is powerful enough to handle the updates. We'll have to introduce a tiny delay for all these cases. IIRC, React introduced deferred value and transition hooks to handle similar cases, but those probably aren't suitable for our case. As @jsnajdr mentioned, eliminating all possible bottlenecks before we reach out for debounce/throttle is probably a better solution. P.S. I tried to analyze performance calls for RichText before without any luck 😅 I understand this isn't easy to track down and fix. |
My first question would be, are there any reliable steps to reproduce? When typing into a paragraph block, for me personally, the editor is able to keep up with my typing speed very well 🙂 Does "typing very very fast" mean a "human" typing speed, or do I need a machine (e2e test) to type fast enough? |
Yes, I have added some very reliable steps to reproduce it in the PR description. The caveat is that you need a very large post, and only then when QUICKLY typing over 50-60 characters, you'll see the error occur. We could be able to achieve it with an e2e test too, since it's fairly straightforward to start with a large post and type quickly there.
Yeah, I think it could potentially be a complex one, and I have limited capacity over the coming weeks, but I'll try to find a solid time slot to debug it more thoroughly. |
I can consistently reproduce React warnings even on small posts when throttling the CPU (4x) and trying to type fast. Based on the trace, I think the error is triggered by |
FWIW the editor in the WordPress mobile app experiences typing performance degradations on lower powered devices and/or while typing swiftly. Mobile devices often have less capable CPUs. Our research findings on this subject also appeared to suggest that updating the global store — which I believe occurs on every keystroke — was the primary culprit for the performance issues when typing in the mobile editor. Notably, re-renders resulting from store updates were seemingly not the issue, but merely updating the global store itself. We briefly explored debouncing the store updates, but encountered issues with rich text values becoming out of date or incorrect. Those issues likely stemmed from complexities in the mobile editor Rich Text implementation and reliance upon/communication loops with the Aztec editor, which is not to applicable to the web editor. |
Interesting, thanks! I know that solving these kinds of issues has been why |
I can confirm that the issue is very easy to reproduce with a large post and 4x CPU throttling. I think this is almost certainly a bug in the |
I did a short timeboxed session today, and while I opened an unrelated improvement (#55041), I haven't seen indications that The fun part is that it works well for me locally even if I use a very low debounce wait time. I've pushed a commit just to try out all tests and see the impact. In the meantime, though, I think the solution might be to introduce some sort of throttling mechanism at the lower levels so the updates aren't called that often, or they aren't called if they haven't been processed just yet. Will try to devote some more time, but it might be next week. In the meantime, I know Jarda wanted to dig further into |
+ 100! Think of rich text as native text areas. Would you be debouncing onChange? The performance problems are not RichText. Maybe debounce all store changes instead? I would prefer a more general solution and leaving RichText alone. |
Thanks for the feedback! I think we all agree and will seek a different alternative. I'm keeping this PR open only for reference at this point. Ideally, we'll be able to trace this to a bug with |
I did some investigation today and found that:
The thing that seems to cause the performance issues is the fact that on a large post with 1000 blocks, there are 80000 listeners on the There is async mode in When profiling, I noticed two other things that stand out there, although they are unlikely to have impact on this specific issue:
|
The animation frames (well not really more idle callbacks) are non blocking, if the user continues to type, they get pushed to later. If you try without the Async mode, typing is actually way worse. |
Yes, that's true, I wanted to say something else: that the typing is slow on the 1000-blocks post even with almost all the blocks in async mode. In other words, only the handful visible blocks are sufficient to make it slow. The 80000 store subscriptions are caused mostly by the const customColors = useSetting( 'color.palette.custom' );
const themeColors = useSetting( 'color.palette.theme' );
const defaultColors = useSetting( 'color.palette.default' );
const shouldDisplayDefaultColors = useSetting( 'color.defaultPalette' );
const customGradients = useSetting( 'color.gradients.custom' );
const themeGradients = useSetting( 'color.gradients.theme' );
const defaultGradients = useSetting( 'color.gradients.default' );
const shouldDisplayDefaultGradients = useSetting( 'color.defaultGradients' ); there's also the Each The There are two ways how to move forward:
|
this is all interesting work, because I've been hunting for a long time major debounce-related issues and the source of tens of thousands of listeners that run after keypresses. I've recorded video of the block editor (with third-party extensions) taking 112s to enter the characters I've typed, after I stop typing. one thing that surprised me was that I found out that often the mouse and sometimes an enter keypress get handled instantly. in fact, in the situations I'm experiencing this latency, I can click into another paragraph or UI element and the keypresses will continue to pour out. there are certain components, however, that flush out all remaining keypresses in an instant: the block inserter search box is one such element. if I have keypresses backed up and they are slowly flushing out, then click in the search box in the block inserter, they all appear simultaneously and the search runs. while I haven't been able to catch this in a vanilla build of Gutenberg, I wonder if you think this is likely the same problem. I've been suspecting that something is queueing up keypresses to hide other performance issues, but once it starts queuing faster than it can flush things get really bad really quickly. |
@dmsnell |
I don't understand the exact differences between mouse events and key events and why some are handled instantly and some not. I believe the editor is too complex for this to be tractable. What I found to be a problem is that we have approx 80 For a post with 1000 blocks, this leads to calling 80k store listeners on each action dispatch. The fact that the vast majority of blocks is in async mode, it doesn't help as much as one would expect. Even adding a new item to the priority queue is not a trivial operation, it is a It's a recording of the loop that calls the 80k listeners. The light green stripes are calls to So, what we need to do is to reduce these 80k listeners to some smaller number. One approach would be a major architecture overhaul. Don't subscribe to the entire In other words, we could break the But I believe we can make significant progress even with the existing Redux architecture. In #55337, by optimizing the One place that I think is very inefficient currently is how we use const validFoos = useSelect( ( select ) => {
/* Complex selection from block-editor store */
} );
return (
<>
<BlockControls>
<FooControl foos={ validFoos } />
<BlockControls>
<Edit />
</>
); Here, every block instance does one or multiple Ideally the |
Not sure you meant to close this one with #55340 but I'm fine with closing it anyway - it wasn't meant to move forward. |
GitHub closed this automatically when it saw "fix X" in a sentence 🙂 I think we'll continue discussing in this PR for some time. |
The feature controls implemented via hooks ( Multiple features call Here's an example optimization: #55345. |
Based on my recent tests (#56101), I believe we've reduced the block editor store listeners by 50% to 40k. |
What?
This PR attempts to debounce
useInput
inside the rich text component in order to resolve an issue when typing quickly. In my testing, it also improves the performance when typing quickly - the typed characters start appearing faster and there's way smaller delay between them.Also related to #22822.
Why?
There are a few different problems this PR is attempting to improve:
1. React warning Maximum update depth exceeded error
When working with a large post, and typing very very fast inside a paragraph, we can see this error in the console:
Since this will debounce the
onInput()
function, it resolves that issue sinceonInput()
no longer calls that often when typing swiftly.2. Typing performance
When working with a large post, and typing very very fast inside a paragraph, we can see that typing gets slower and the delay between each character that gets added gets bigger and bigger. I see a huge improvement when testing with this branch.
How?
We're simply debouncing
useInput()
so it won't be called that often, and that way it won't cause infinite loops and delayed typing feedback.May be easier to review with ignored whitespace.
Testing Instructions
Testing Instructions for Keyboard
None
Screenshots or screencast
None