Open Bug 667412 Opened 13 years ago Updated 3 months ago

Loading lots of text in a narrow textarea is very slow

Categories

(Core :: Layout: Block and Inline, defect)

x86
Linux
defect

Tracking

()

People

(Reporter: smontagu, Unassigned)

References

(Depends on 1 open bug)

Details

(Keywords: perf)

Attachments

(2 files)

Attached file Testcase
I encountered this when trying to assess performance with the patch for bug 664087, and also whether recent bidi performance work has fixed bug 376359, but this is not a bidi issue -- this version of the testcase is LTR text only and still shows the problem. In a profile of loading the testcase, 77.4% is spent in nsLineBox::RFindLineContaining, called from nsBlockFrame::AddFrames. Apparently we are hitting the "less common" case referred to in the comment there: // XXX_perf This is technically O(N^2) in some cases, but by using // RFind instead of Find, we make it O(N) in the most common case, // which is appending content.
Ah, this is an interesting case. So the point here is that the textarea has tons of lines and that some of them are longer than the textarea width. Each such line causes us to wrap and create a continuation, and then we have to find the right line to put that continuation in. And we do the search in reverse, but the continuations are inserted all over, so it ends up being O(N^2). Messing with the search order would not help here, of course. fantasai, do you think we can try to do better here after the split? If so, we should add a dep.
Component: Layout → Layout: Block and Inline
QA Contact: layout → layout.block-and-inline
Summary: Loading lots of text in a textarea is very slow → Loading lots of text in a narrow textarea is very slow
What I was thinking was that when we call InsertFrames from nsTextFrame::SetLength we already know the line (at least some of the time), so maybe we could pass it in and avoid the search?
We could try... This seems like a situation that would be helped by something like a line cursor, actually: during reflow, search forward from the line currently being reflowed.
Passing in the line helps some, but it leaves us spending 44% of the time in nsLineBox::IndexOf, called from BuildTextRuns via the nsBlockInFlowLineIterator ctor.
Component: Layout: Block and Inline → Layout
Component: Layout → Layout: Block and Inline
I doubt the split would make a difference in what we can do here.
OK. Do we have existing bugs on making it possible to add kids to a block without finding the right line? I seem to recall some proposals about that...
Blocks: 680461
No longer blocks: 680461
This might be the same issue as bug 561578.
Depends on: 561578
Or more precisely, the underlying problem is the same: bug 190147.
Depends on: 190147
Depends on: 682052
A much shorter yet reproducible testcase on any textarea (not specially narrow) seems to be Github's stylesheet ( https://a248.e.akamai.net/assets.github.com/stylesheets/bundle_github.css - 'only' 300kB with very long lines).
The key part for this bug is that the textarea be narrow compared to the width of a typical line so there are lots of continuations being created.
As far as I can tell, fixing bug 682052 is the only way that this will ever be fixed, right?
That's the only way this will get _closer_ to being fixed. Once we fix that, we might find another hotspot. :(
Current Gecko Profiler trace says 76% in nsLineBox::RFindLineContaining() (no surprise), and 17% in nsTextFrame::LastInFlow(), and 2% in BuildTextRuns()
Severity: normal → S3

Nightly openiing the testcase: https://share.firefox.dev/4cHGKQ6 (8.4s)
Nightly resizing the textbox to make it narrow: https://share.firefox.dev/3T2DWWZ (infinite time)
Chrome opening the testcase: 4.4s

Curious..... I see much better performance than that in my Nightly on macOS. Here's a profile of opening the testcase and then dragging the corner of the textbox around to resize it (including making it very narrow): https://share.firefox.dev/471iMht

The initial reflow is around 2.6s, but after that reflows are generally in the 100-120ms range, and it feels pretty responsive. I wonder why your profiles are so different.

I retested, and I sometimes get "good" profiles where the resizes are pretty smooth. And sometimes I get "bad" profiles where the resizes take a looooong time. No idea why there would be this dual behaviour.

Good: https://share.firefox.dev/3Xh3li0
Bad: https://share.firefox.dev/3YWGcCN (loooong time)

Edit: In this profile, it was initially very smooth. But then it became very slow.
https://share.firefox.dev/3z26Qzv

This may be that if you continue to resize without letting go of the resize handle, it will be fast. But if you release the handle (and myabe switch tabs) and then retry, the reflows are slow? Almost like doing an action inadvertently flushes away some cache.

That's really strange.

I tried it on my Win10 machine too: the initial reflow was 3.3s, and subsequent reflows while resizing the textarea are in the hundred-millisecond range (occasionally as high as 200ms, but generally less).

So I'm puzzled why you're sometimes seeing that "infinite time" case when resizing the textbox; so far, I haven't been able to reproduce that.

In all the profiles, though, it's clear that accessing nsTextFrame::LastInFlow() is much too costly. Maybe we can do something about that.

Another example where it started as fast, but then became very slow: https://share.firefox.dev/4fWZ9va

Attached file about:support

I think it has to do something with change of focus. If you resize it quickly and then alt+tab to another window, or maybe Ctrl+tab to another tab, then it seems to get stuck when you return to the demo tab.
Similarly, if your resize is stuck, switching to another tab or window and then switching back again seems to fix it.

Profile where I did all of that and with graphics preset logging: https://share.firefox.dev/3YYeXI2

(In reply to Mayank Bansal from comment #14)

Chrome opening the testcase: 4.4s

(For completeness: Chrome also janks for ~4 seconds when I resize the textbox to make it as narrow as possible.)

(In reply to Jonathan Kew [:jfkthame] from comment #15)

Curious..... I see much better performance than that in my Nightly on macOS. [...] and it feels pretty responsive. I wonder why your profiles are so different.

I think to trigger badness, you need to release your mouse button when the textarea is skinny. While you're dragging, I think we use interruptible reflow which makes it remain responsive as you keep dragging it (so we might start an expensive reflow, but we bail out as you resize the textarea to be wider). But when you release your mouse button, we do a non-interruptible reflow, which is extremely slow for a skinny textarea.

Maybe try opening the browser and another program like notepad side-by-side.
Click on the notepad then quickly resize the testcase. Then quickly click on the notepad again. Rinse and repeat.
If you do manage to get your resize stuck, click on the notepad and then click back to the browser. It should immediately get "unstuck".

Profile: https://share.firefox.dev/3XdmbXg

Here's a profile I just captured to demonstrate this being responsive in part of the profile and also hanging in another part of the profile:
https://share.firefox.dev/3Axh8YO

In this profile:

  • For the first half, I shrink+grow the textarea (as skinny as possible and then back to wide again), releasing my mousebutton when it's wide.
  • For the last half, I shrink the textarea, releasing my mousebutton when it's skinny.
  • There's a final PresShell::DoFlushPendingNotifications Layout operation (when I release my mousebutton) at the end of each of these resize operations; it's only 216ms long when I leave the textarea in a wide state, vs. it's 16s long when I leave the textarea in a skinny state. (Note that I didn't quite make it as skinny as possible; it would be worse if I did that.)
  • All the other reflows are PresShell::DoFlushPendingNotifications InterruptibleLayout to keep things responsive during my drag operation. (So they might be theoretically slow reflows, but we're able to bail out of them.)

Thanks for the added profiling details; given this, I see comparable results locally as well.

I wondered if we might be able to improve this by some kind of caching to accelerate nsTextFrame::LastInFlow(), which shows up as very hot in the profiles. But so far, no useful progress. :(

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: