Closed
Bug 241796
Opened 20 years ago
Closed 18 years ago
Painting large tables requires linear walk over all rows (slow table scrolling)
Categories
(Core :: Web Painting, defect)
Core
Web Painting
Tracking
()
RESOLVED
FIXED
People
(Reporter: bzbarsky, Assigned: roc)
References
()
Details
(Keywords: perf)
Attachments
(3 files)
26.75 KB,
patch
|
bzbarsky
:
review+
bzbarsky
:
superreview+
|
Details | Diff | Splinter Review |
54.19 KB,
application/zip
|
Details | |
4.54 KB,
patch
|
bzbarsky
:
review+
bzbarsky
:
superreview+
|
Details | Diff | Splinter Review |
This is basically the table analogue of bug 51938. Now that we've fixed bug 240275, scrolling a page with a large table is still rather painful due to this issue. The solution would be to do what blocks do -- store a cursor, etc. For a table, the .ys for rows should be nondecreasing, so we only have to worry about YMosts. I'm not sure whether we want to take exactly the same approach as for blocks (since that will effectively turn off the optimization on any table with a cell that has a rowspan greater than 2); maybe we can handle those cases?
Assignee | ||
Comment 1•20 years ago
|
||
> For a table, the .ys for rows should be nondecreasing No, because we're talking about the overflow area. > I'm not sure whether we want to take exactly the same approach as > for blocks (since that will effectively turn off the optimization on any table > with a cell that has a rowspan greater than 2) We could boost the height of the overflow area of a row to at include the bottom of any rowspanning cells that cover the row. Then we could use the same technique as for blocks. Bernd, would that be hard?
> I'm not sure whether we want to take exactly the same approach as for blocks So does that mean we add the same code twice for blocks and for tables? Shouldnt that then go the HTMLContainer? I would like to avoid adding special code to the tables. What I have done over the past months is just the opposite. Remove as much as possible special handling, make table frames nice citizens of the frame land. This is necessary as in a foreseeable future the amount of engineer hours spend in the layout-table will be limited. We have some cellmap functions like http://lxr.mozilla.org/seamonkey/ident?i=RowIsSpannedInto and http://lxr.mozilla.org/seamonkey/ident?i=RowHasSpanningCells They are of course potential performance bombs especially in the case when you dont have rowspans. I have strong feelings about horking the row overflow area. Returning to the first point I dont see why a large nubmer of blocks with vertical overflow wouldn't pose the same problem.
Reporter | ||
Comment 3•20 years ago
|
||
> So does that mean we add the same code twice for blocks and for tables? We sorta have to do it anyway, since blocks have this line iterator funkiness going on, so in blocks the basic child is not a frame but a nsLineBox, while in tables it is in fact a frame (table row frame, in fact).... > They are of course potential performance bombs especially in the case when you > dont have rowspans. Yeah, calling those on every Paint() or worse yet GetFrameForPoint() operation would be moderately unpleasant... Let's not. > I have strong feelings about horking the row overflow area. Reasonable sentiment. > I dont see why a large nubmer of blocks with vertical overflow wouldn't pose > the same problem They would. I posit (perhaps incorrectly?) that having a large number of block kids at least one of which overflows is rarer than having a large number of rows and at least one row-spanning cell. But both should be rare, so maybe we just want to start with a basic impl a la blocks and not deal with the hard and rare cases for now.
speaking of GetFrameForPoint it seems that bug 219147 matures for prime time.
Assignee | ||
Comment 5•20 years ago
|
||
> So does that mean we add the same code twice for blocks and for tables?
> Shouldnt that then go the HTMLContainer?
Like bz said ... it's the same general concept but quite different code. The
invariant will probably be different, what we think of as children will be
different, how you iterate over them will be different.
The block cursor doesn't handle all cases. The idea is to handle common cases
well with a small amount of maintainable code, and make the solution more
general (and more complex) only when we can see a very clear need.
Actually I think I have worked out a general solution that's not too complex :-)
Assignee | ||
Comment 6•20 years ago
|
||
here's the idea: check that the sequences of row frame .y and .YMost values are both non-decreasing (that's the regular frame rect, not the overflow area). compute maxOverflowAbove = max over row frames (-overflowArea.x) maxOverflowBelow = max over row frames (overflowArea.XMost - rect.XMost) now, the first row frame that can overlap a given vertical y is the first row frame whose rect includes y - maxOverflowAbove. the last row frame that can overlap a given vertical y is the last row frame whose rect includes y + maxOverflowBelow.
Assignee | ||
Comment 7•20 years ago
|
||
Ooh, I just realized one thing: for the cursor stuff to work, we have to be able to find the preceding row of a table row frame in constant time. Can we do that? (In blocks we have a doubly-linked list of lines.)
no table frames are just nice frames and live in frame lists. see: nsTableFrame::GetFrameAtOrBefore http://lxr.mozilla.org/seamonkey/source/layout/html/table/src/nsTableFrame.cpp#4267 just as a side note imagine that you pass as nsLayoutAtoms::tableColFrame aChildType and then hit line 4276 or 4289
Assignee | ||
Comment 9•20 years ago
|
||
Yickity. Well, how hard would it be to keep the rows in a linked list? Do normal tables have one row group per row or one row group per table?
Reporter | ||
Comment 10•20 years ago
|
||
One row group per table. So we're interested in nsRowGroupFrame::Paint and nsRowGroupFrame::GetFrameForPoint, not in nsTableFrame.
Comment 11•20 years ago
|
||
http://bugzilla.mozilla.org/show_bug.cgi?id=233463#c25 isnt point 6 the way to go?
Reporter | ||
Comment 12•20 years ago
|
||
Only if we also implement an (efficient) prev() like blocks do.
Assignee | ||
Comment 13•20 years ago
|
||
Sure, we could abstract out an iterator for rows, but I think that wouldn't buy us much. The line iterators just confuse me. I think a mPrev field in nsTableRowFrame and an mLastRow field in nsTableRowGroupFrame would be just fine.
Reporter | ||
Comment 14•20 years ago
|
||
Who'd maintain that mPrev? Especially in the context of the changes I think we need to make in bug 233463?
Assignee | ||
Comment 15•20 years ago
|
||
nsTableRowGroupFrame would maintain it, I guess.
Assignee | ||
Comment 16•20 years ago
|
||
If we want to use this as an opportunity to prototype an nsReversibleFrameList for later use elsewhere, I'd be for that.
Updated•20 years ago
|
Reporter | ||
Updated•18 years ago
|
Summary: Painting large tables requires linear walk over all rows → Painting large tables requires linear walk over all rows (slow table scrolling)
Assignee | ||
Comment 17•18 years ago
|
||
I think I can just use GetPrevInFlow() to get the previous row in the rowgroup, plus a check to make sure we're still in the same rowgroup.
Assignee | ||
Comment 18•18 years ago
|
||
Never mind, I'm stupid. If we do have to go back above the current row then I'll just build a temporary row array. So this would be O(N) in the number of rows, but with a small constant.
Assignee | ||
Comment 19•18 years ago
|
||
Actually I can keep an array of frame pointers alongside the cursor. We'll clear the cursor anyway anytime something happens that could change the array.
Assignee | ||
Comment 20•18 years ago
|
||
This patch adds cursor support to nsTableRowGroupFrame. It seems to work, but I don't have any testcases that show a measurable performance improvement...
Reporter | ||
Comment 21•18 years ago
|
||
Damn. I wish I'd saved the original testcase. Let me see whether I can create a testcase for this.
Reporter | ||
Comment 22•18 years ago
|
||
With this testcase, and without this patch, I see a good chunk (30+%) of the time spent in (not under) nsIFrame::BuildDisplayListForChild. Applying this patch drops that to more like 0.3%. The next hotspot is nsTableRowFrame::GetNextRow() called from TableBackgroundPainter::PaintRowGroup()... which does a loop over all rows. It should probably make use of the cursor too. In all, with this patch applied about 75% of the time is spent in non-painting stuff (GetNextRow and TableBackgroundPainter::TableBackgroundData::SetFrame() under PaintRowGroup.
Assignee | ||
Comment 23•18 years ago
|
||
Do you think this is worth taking, then?
Reporter | ||
Comment 24•18 years ago
|
||
I think so; actual performance on the testcase I attached (and on any other table with tens of thousands of rows) is a lot better (probably 2-3 times as fast). We could do a followup for PaintRowGroup, I guess.
Assignee | ||
Comment 25•18 years ago
|
||
Comment on attachment 235872 [details] [diff] [review] fix okay then!
Attachment #235872 -
Flags: superreview?(bzbarsky)
Attachment #235872 -
Flags: review?(bzbarsky)
Reporter | ||
Comment 26•18 years ago
|
||
I should get to this early next week.
Reporter | ||
Comment 27•18 years ago
|
||
Comment on attachment 235872 [details] [diff] [review] fix >Index: layout/tables/nsTableFrame.h >+ * @param aCursorFrame if non-null, this is the frame to start with, and we This should be documentation for aTraversal, right? >Index: layout/tables/nsTableRowGroupFrame.cpp >+DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, After you finish setting up the row cursor, do you want to call Compact() on its mFrames? nsTArray uses a doubling algorithm for EnsureCapacity, so we could have a lot of wasted space in the array. >+void nsTableRowGroupFrame::ClearRowCursor() { Linebreaks after the |void| and before the '{', please. >+nsTableRowGroupFrame::FrameCursorData* nsTableRowGroupFrame::SetupRowCursor() { Similar here. >+ if (GetStateBits() & NS_ROWGROUP_HAS_ROW_CURSOR) >+ return nsnull; Curly braces around the if body, please, and same elsewhere in the patch. And add a comment explaining that if someone's asking for a cursor when we already have one it's because they shouldn't be using it anyway, so we return null? Or something along those lines? Just so some kind soul doesn't decide to "fix" this. >+nsIFrame* nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY, nscoord* aOverflowAbove) { Again, linebreaks after the return type and before the curly. >+ while (cursorIndex > 0 && >+ cursorFrame->GetRect().YMost() + property->mOverflowBelow > aY) { So I assume there's a reason we don't need the IsEmpty() check on the rect here because we perform it in AppendFrame, right? Also, how come we use mOverflowBelow here and not just the cursorFrame's overflow YMost()? I assume this is to handle a case where some row doesn't reach down to aY, but a row above it with a rowspanning cell does? If so, we should document that... >Index: layout/tables/nsTableRowGroupFrame.h >+ FrameCursorData() >+ : mCursorIndex(0), mOverflowAbove(0), mOverflowBelow(0) {} You know you'll have at least MIN_ROWS_NEEDING_CURSOR frames, right? Might as well toss in |mFrames(MIN_ROWS_NEEDING_CURSOR)| and avoid some allocations. >+ void ClearRowCursor(); >+ /** Put a linebreak in there? >+ * Get the first row that might contain y-coord 'aY', or nsnull if you must search Extra space before "Get". >+ * overflowArea.ys and overflowArea.yMosts are non-decreasing. So this is only the case because the caller of AppendFrame enforces it, right? >+ * You can stop when you reach a row whose GetRect().y - aOverflowAbove is *aOverflowAbove, right? And "stop" means "stop looking for rows that contain aY"? >+ nsIFrame* GetFirstRowContaining(nscoord aY, nscoord* aOverflowAbove); >+ /** Again, put in a linebreak. r+sr=bzbarsky with those nits picked.
Attachment #235872 -
Flags: superreview?(bzbarsky)
Attachment #235872 -
Flags: superreview+
Attachment #235872 -
Flags: review?(bzbarsky)
Attachment #235872 -
Flags: review+
Assignee | ||
Comment 28•18 years ago
|
||
(In reply to comment #27) > >Index: layout/tables/nsTableFrame.h > >+ * @param aCursorFrame if non-null, this is the frame to start with, and we > > This should be documentation for aTraversal, right? Er, right. > >Index: layout/tables/nsTableRowGroupFrame.cpp > > >+DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, > > After you finish setting up the row cursor, do you want to call Compact() on > its mFrames? nsTArray uses a doubling algorithm for EnsureCapacity, so we > could have a lot of wasted space in the array. Good idea > >+ if (GetStateBits() & NS_ROWGROUP_HAS_ROW_CURSOR) > >+ return nsnull; > > Curly braces around the if body, please, and same elsewhere in the patch. I thought we were allowing bare returns, breaks and continues to not have their own block. > And add a comment explaining that if someone's asking for a cursor when we > already have one it's because they shouldn't be using it anyway, so we return > null? Or something along those lines? Just so some kind soul doesn't decide > to "fix" this. OK. The idea is that if someone calls SetupRowCursor while we already have one, we let them go ahead but we prevent them from messing with the existing cursor. This can happen when GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO is set (when we are forced to traverse all rows looking for placeholders of absolutely or fixed positioned elements that need to be painted). > >+ while (cursorIndex > 0 && > >+ cursorFrame->GetRect().YMost() + property->mOverflowBelow > aY) { > > So I assume there's a reason we don't need the IsEmpty() check on the rect here > because we perform it in AppendFrame, right? Exactly. I'll add a comment. > Also, how come we use mOverflowBelow here and not just the cursorFrame's > overflow YMost()? I assume this is to handle a case where some row doesn't > reach down to aY, but a row above it with a rowspanning cell does? If so, we > should document that... Right. It's essential that the sequence of thresholds we test aY against be monotonic, otherwise we can't know when to stop. > >Index: layout/tables/nsTableRowGroupFrame.h > >+ FrameCursorData() > >+ : mCursorIndex(0), mOverflowAbove(0), mOverflowBelow(0) {} > > You know you'll have at least MIN_ROWS_NEEDING_CURSOR frames, right? Might as > well toss in |mFrames(MIN_ROWS_NEEDING_CURSOR)| and avoid some allocations. Good idea. > >+ * overflowArea.ys and overflowArea.yMosts are non-decreasing. > > So this is only the case because the caller of AppendFrame enforces it, right? Actually this comment is wrong, it's a leftover from the nsBlockFrame regime. There is no such guarantee. The only relevant guarantee is that you can start from the returned frame, traversing the sibling chain, and stop when reach a row whose GetRect().y - aOverflowAbove is >= the YMost() of the vertical region you're interested in, and be sure that you have seen all the frames whose overflow area intersects with your vertical region. > >+ * You can stop when you reach a row whose GetRect().y - aOverflowAbove is > > *aOverflowAbove, right? Right > And "stop" means "stop looking for rows that contain aY"? Stop looking for rows that intersect your vertical region. I'll clean this up to make it a lot more precise. Thanks!
Assignee | ||
Comment 29•18 years ago
|
||
(In reply to comment #28) > OK. The idea is that if someone calls SetupRowCursor while we already have one, > we let them go ahead but we prevent them from messing with the existing cursor. > This can happen when GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO > is set (when we are forced to traverse all rows looking for placeholders of > absolutely or fixed positioned elements that need to be painted). This is actually an optimization, BTW. It means we don't eat the cost of reconstructing the cursor if we already have a perfectly good one.
Assignee | ||
Comment 30•18 years ago
|
||
checked in.
Status: NEW → RESOLVED
Closed: 18 years ago
Resolution: --- → FIXED
Reporter | ||
Comment 31•18 years ago
|
||
So I'm not following this comment in nsTableFrame.h: 268 * @param aTraversal if non-null, this is the frame to start with, and we 269 * can stop as soon as we find a frame whose overflowRect has y-coordinate 270 * below the dirty rect and is non-empty And I just realized that the SetProperty in nsTableRowGroupFrame::SetupRowCursor can fail and that we should handle that, probably with a NS_UNLIKELY check... Also, is the stuff about YMost in the comments on SetupRowCursor correct? Similar wording was removed from the GetFirstRowContaining docs... aTraversal seems to be a function...
Reporter | ||
Comment 32•18 years ago
|
||
> aTraversal seems to be a function... This was supposed to be after the quote from nsTableFrame.h and before the other comments in comment 31.
Assignee | ||
Comment 33•18 years ago
|
||
(In reply to comment #31) > Also, is the stuff about YMost in the comments on SetupRowCursor correct? > Similar wording was removed from the GetFirstRowContaining docs... It is, because we're talking about the frame Y and YMost, not the overflowArea. I'll post a patch to address the other issues.
Assignee | ||
Comment 34•18 years ago
|
||
Fixes comment, handles SetProperty failure.
Attachment #239292 -
Flags: superreview?(bzbarsky)
Attachment #239292 -
Flags: review?(bzbarsky)
Reporter | ||
Comment 35•18 years ago
|
||
Comment on attachment 239292 [details] [diff] [review] fix outstanding issues Looks good
Attachment #239292 -
Flags: superreview?(bzbarsky)
Attachment #239292 -
Flags: superreview+
Attachment #239292 -
Flags: review?(bzbarsky)
Attachment #239292 -
Flags: review+
Comment 36•18 years ago
|
||
Should this bug be resolved fixed? I don't see a checkin for the attachment 239292 [details] [diff] [review] 'fix outstanding issues'. Sorry if I just overlooked it.
Assignee | ||
Comment 37•18 years ago
|
||
The bug is fixed. The remaining patch just cleans up some stuff. I'll check it in today.
Assignee | ||
Comment 38•18 years ago
|
||
checked in.
Updated•6 years ago
|
Component: Layout: View Rendering → Layout: Web Painting
You need to log in
before you can comment on or make changes to this bug.
Description
•