From 7213c84c26d4c6ac1993b3bec0c5b68b44c3d1fa Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Sun, 11 Mar 2012 03:32:27 +0100 Subject: [PATCH] Bug 730769 - Make nsLineBox use a frame hash table for lines with many frames. part=2/2 r=bz --- layout/generic/nsBlockFrame.cpp | 43 +++++------- layout/generic/nsBlockFrame.h | 7 +- layout/generic/nsLineBox.cpp | 110 ++++++++++++++++++++++++----- layout/generic/nsLineBox.h | 119 ++++++++++++++++++++++++++------ 4 files changed, 212 insertions(+), 67 deletions(-) diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index 6742eea928d6..3af971276632 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -2671,16 +2671,14 @@ nsBlockFrame::PullFrameFrom(nsBlockReflowState& aState, } // when aFromContainer is 'this', then aLine->LastChild()'s next sibling // is already set correctly. - aLine->SetChildCount(aLine->GetChildCount() + 1); - - PRInt32 fromLineChildCount = fromLine->GetChildCount(); - if (0 != --fromLineChildCount) { + aLine->NoteFrameAdded(frame); + + if (fromLine->GetChildCount() > 1) { // Mark line dirty now that we pulled a child - fromLine->SetChildCount(fromLineChildCount); + fromLine->NoteFrameRemoved(frame); fromLine->MarkDirty(); fromLine->mFirstChild = newFirstChild; - } - else { + } else { // Free up the fromLine now that it's empty // Its bounds might need to be redrawn, though. // XXX WHY do we invalidate the bounds AND the combined area? doesn't @@ -3305,7 +3303,7 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState, // Push continuation to a new line, but only if we actually made one. if (madeContinuation) { - nsLineBox* line = NewLineBox(nextFrame, 1, true); + nsLineBox* line = NewLineBox(nextFrame, true); NS_ENSURE_TRUE(line, NS_ERROR_OUT_OF_MEMORY); mLines.after_insert(aLine, line); } @@ -3976,7 +3974,7 @@ nsBlockFrame::CreateContinuationFor(nsBlockReflowState& aState, mFrames.InsertFrame(nsnull, aFrame, newFrame); if (aLine) { - aLine->SetChildCount(aLine->GetChildCount() + 1); + aLine->NoteFrameAdded(newFrame); } aMadeNewFrame = true; @@ -4099,12 +4097,11 @@ nsBlockFrame::SplitLine(nsBlockReflowState& aState, #endif // Put frames being split out into their own line - nsLineBox* newLine = NewLineBox(aFrame, pushCount, false); + nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount); if (!newLine) { return NS_ERROR_OUT_OF_MEMORY; } mLines.after_insert(aLine, newLine); - aLine->SetChildCount(aLine->GetChildCount() - pushCount); #ifdef DEBUG if (gReallyNoisyReflow) { newLine->List(stdout, gNoiseIndent+1); @@ -4920,12 +4917,11 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling) PRInt32 rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1; if (rem) { // Split the line in two where the frame(s) are being inserted. - nsLineBox* line = NewLineBox(aPrevSibling->GetNextSibling(), rem, false); + nsLineBox* line = NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem); if (!line) { return NS_ERROR_OUT_OF_MEMORY; } lineList->after_insert(prevSibLine, line); - prevSibLine->SetChildCount(prevSibLine->GetChildCount() - rem); // Mark prevSibLine dirty and as needing textrun invalidation, since // we may be breaking up text in the line. Its previous line may also // need to be invalidated because it may be able to pull some text up. @@ -4966,7 +4962,7 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling) (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) { // Create a new line for the frame and add its line to the line // list. - nsLineBox* line = NewLineBox(newFrame, 1, isBlock); + nsLineBox* line = NewLineBox(newFrame, isBlock); if (!line) { return NS_ERROR_OUT_OF_MEMORY; } @@ -4982,7 +4978,7 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling) } } else { - prevSibLine->SetChildCount(prevSibLine->GetChildCount() + 1); + prevSibLine->NoteFrameAdded(newFrame); // We're adding inline content to prevSibLine, so we need to mark it // dirty, ensure its textruns are recomputed, and possibly do the same // to its previous line since that line may be able to pull content up. @@ -5504,9 +5500,7 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags) } // Update the child count of the line to be accurate - PRInt32 lineChildCount = line->GetChildCount(); - lineChildCount--; - line->SetChildCount(lineChildCount); + line->NoteFrameRemoved(aDeletedFrame); // Destroy frame; capture its next continuation first in case we need // to destroy that too. @@ -5533,7 +5527,7 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags) bool haveAdvancedToNextLine = false; // If line is empty, remove it now. - if (0 == lineChildCount) { + if (0 == line->GetChildCount()) { #ifdef NOISY_REMOVE_FRAME printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n", searchingOverflowList?"overflow":"normal", line.get()); @@ -5686,12 +5680,10 @@ nsBlockFrame::StealFrame(nsPresContext* aPresContext, } // Register removal with the line boxes - PRInt32 count = line->GetChildCount(); - line->SetChildCount(--count); - if (count > 0) { + line->NoteFrameRemoved(frame); + if (line->GetChildCount() > 0) { line->MarkDirty(); - } - else { + } else { // Remove the line box nsLineBox* lineBox = line; if (searchingOverflowList) { @@ -5710,8 +5702,7 @@ nsBlockFrame::StealFrame(nsPresContext* aPresContext, line_end = mLines.end(); line = line_end; } - } - else { + } else { line = mLines.erase(line); } lineBox->Destroy(aPresContext->PresShell()); diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h index 2ac0cd4ede76..dfbb1ec62072 100644 --- a/layout/generic/nsBlockFrame.h +++ b/layout/generic/nsBlockFrame.h @@ -354,8 +354,11 @@ protected: #endif #endif - nsLineBox* NewLineBox(nsIFrame* aFrame, PRInt32 aCount, bool aIsBlock) { - return NS_NewLineBox(PresContext()->PresShell(), aFrame, aCount, aIsBlock); + nsLineBox* NewLineBox(nsIFrame* aFrame, bool aIsBlock) { + return NS_NewLineBox(PresContext()->PresShell(), aFrame, aIsBlock); + } + nsLineBox* NewLineBox(nsLineBox* aFromLine, nsIFrame* aFrame, PRInt32 aCount) { + return NS_NewLineBox(PresContext()->PresShell(), aFromLine, aFrame, aCount); } void FreeLineBox(nsLineBox* aLine) { aLine->Destroy(PresContext()->PresShell()); diff --git a/layout/generic/nsLineBox.cpp b/layout/generic/nsLineBox.cpp index 6ef3a1fc40bc..728b2d6eac17 100644 --- a/layout/generic/nsLineBox.cpp +++ b/layout/generic/nsLineBox.cpp @@ -55,6 +55,11 @@ static PRInt32 ctorCount; PRInt32 nsLineBox::GetCtorCount() { return ctorCount; } #endif +#ifndef _MSC_VER +// static nsLineBox constant; initialized in the header file. +const PRUint32 nsLineBox::kMinChildCountForHashtable; +#endif + nsLineBox::nsLineBox(nsIFrame* aFrame, PRInt32 aCount, bool aIsBlock) : mFirstChild(aFrame), mBounds(0, 0, 0, 0), @@ -76,7 +81,7 @@ nsLineBox::nsLineBox(nsIFrame* aFrame, PRInt32 aCount, bool aIsBlock) #if NS_STYLE_CLEAR_NONE > 0 mFlags.mBreakType = NS_STYLE_CLEAR_NONE; #endif - SetChildCount(aCount); + mChildCount = aCount; MarkDirty(); mFlags.mBlock = aIsBlock; } @@ -84,14 +89,88 @@ nsLineBox::nsLineBox(nsIFrame* aFrame, PRInt32 aCount, bool aIsBlock) nsLineBox::~nsLineBox() { MOZ_COUNT_DTOR(nsLineBox); + if (NS_UNLIKELY(mFlags.mHasHashedFrames)) { + delete mFrames; + } Cleanup(); } nsLineBox* -NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame, - PRInt32 aCount, bool aIsBlock) +NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame, bool aIsBlock) { - return new (aPresShell)nsLineBox(aFrame, aCount, aIsBlock); + return new (aPresShell) nsLineBox(aFrame, 1, aIsBlock); +} + +nsLineBox* +NS_NewLineBox(nsIPresShell* aPresShell, nsLineBox* aFromLine, + nsIFrame* aFrame, PRInt32 aCount) +{ + nsLineBox* newLine = new (aPresShell) nsLineBox(aFrame, aCount, false); + if (newLine) { + newLine->NoteFramesMovedFrom(aFromLine); + } + return newLine; +} + +void +nsLineBox::StealHashTableFrom(nsLineBox* aFromLine, PRUint32 aFromLineNewCount) +{ + MOZ_ASSERT(!mFlags.mHasHashedFrames); + MOZ_ASSERT(GetChildCount() >= PRInt32(aFromLineNewCount)); + mFrames = aFromLine->mFrames; + mFlags.mHasHashedFrames = 1; + aFromLine->mFlags.mHasHashedFrames = 0; + aFromLine->mChildCount = aFromLineNewCount; + // remove aFromLine's frames that aren't on this line + nsIFrame* f = aFromLine->mFirstChild; + for (PRUint32 i = 0; i < aFromLineNewCount; f = f->GetNextSibling(), ++i) { + mFrames->RemoveEntry(f); + } +} + +void +nsLineBox::NoteFramesMovedFrom(nsLineBox* aFromLine) +{ + PRUint32 fromCount = aFromLine->GetChildCount(); + PRUint32 toCount = GetChildCount(); + MOZ_ASSERT(toCount <= fromCount, "moved more frames than aFromLine has"); + PRUint32 fromNewCount = fromCount - toCount; + if (NS_LIKELY(!aFromLine->mFlags.mHasHashedFrames)) { + aFromLine->mChildCount = fromNewCount; + MOZ_ASSERT(toCount < kMinChildCountForHashtable); + } else if (fromNewCount < kMinChildCountForHashtable) { + // aFromLine has a hash table but will not have it after moving the frames + // so this line can steal the hash table if it needs it. + if (toCount >= kMinChildCountForHashtable) { + StealHashTableFrom(aFromLine, fromNewCount); + } else { + delete aFromLine->mFrames; + aFromLine->mFlags.mHasHashedFrames = 0; + aFromLine->mChildCount = fromNewCount; + } + } else { + // aFromLine still needs a hash table. + if (toCount < kMinChildCountForHashtable) { + // remove the moved frames from it + nsIFrame* f = mFirstChild; + for (PRUint32 i = 0; i < toCount; f = f->GetNextSibling(), ++i) { + aFromLine->mFrames->RemoveEntry(f); + } + } else if (toCount <= fromNewCount) { + // This line needs a hash table, allocate a hash table for it since that + // means fewer hash ops. + nsIFrame* f = mFirstChild; + for (PRUint32 i = 0; i < toCount; f = f->GetNextSibling(), ++i) { + aFromLine->mFrames->RemoveEntry(f); // toCount RemoveEntry + } + SwitchToHashtable(); // toCount PutEntry + } else { + // This line needs a hash table, but it's fewer hash ops to steal + // aFromLine's hash table and allocate a new hash table for that line. + StealHashTableFrom(aFromLine, fromNewCount); // fromNewCount RemoveEntry + aFromLine->SwitchToHashtable(); // fromNewCount PutEntry + } + } } // Overloaded new operator. Uses an arena (which comes from the presShell) @@ -102,21 +181,11 @@ nsLineBox::operator new(size_t sz, nsIPresShell* aPresShell) CPP_THROW_NEW return aPresShell->AllocateMisc(sz); } -// Overloaded delete operator. Doesn't actually free the memory, because we -// use an arena -void -nsLineBox::operator delete(void* aPtr, size_t sz) -{ -} - void nsLineBox::Destroy(nsIPresShell* aPresShell) { - // Destroy the object. This won't actually free the memory, though - delete this; - - // Have the pres shell recycle the memory - aPresShell->FreeMisc(sizeof(*this), (void*)this); + this->nsLineBox::~nsLineBox(); + aPresShell->FreeMisc(sizeof(*this), this); } void @@ -365,10 +434,18 @@ nsLineBox::RFindLineContaining(nsIFrame* aFrame, PRInt32* aFrameIndexInLine) { NS_PRECONDITION(aFrame, "null ptr"); + nsIFrame* curFrame = aLastFrameBeforeEnd; while (aBegin != aEnd) { --aEnd; NS_ASSERTION(aEnd->LastChild() == curFrame, "Unexpected curFrame"); + if (NS_UNLIKELY(aEnd->mFlags.mHasHashedFrames) && + !aEnd->Contains(aFrame)) { + if (aEnd->mFirstChild) { + curFrame = aEnd->mFirstChild->GetPrevSibling(); + } + continue; + } // i is the index of curFrame in aEnd PRInt32 i = aEnd->GetChildCount() - 1; while (i >= 0) { @@ -379,6 +456,7 @@ nsLineBox::RFindLineContaining(nsIFrame* aFrame, --i; curFrame = curFrame->GetPrevSibling(); } + MOZ_ASSERT(!aEnd->mFlags.mHasHashedFrames, "Contains lied to us!"); } *aFrameIndexInLine = -1; return false; diff --git a/layout/generic/nsLineBox.h b/layout/generic/nsLineBox.h index f20023e30878..a024ed48be26 100644 --- a/layout/generic/nsLineBox.h +++ b/layout/generic/nsLineBox.h @@ -182,9 +182,21 @@ protected: need to rearrange the mBits bitfield; #endif -// Funtion to create a line box +/** + * Function to create a line box and initialize it with a single frame. + * If the frame was moved from another line then you're responsible + * for notifying that line using NoteFrameRemoved(). Alternatively, + * it's better to use the next function that does that for you in an + * optimal way. + */ nsLineBox* NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame, - PRInt32 aCount, bool aIsBlock); + bool aIsBlock); +/** + * Function to create a line box and initialize it with aCount frames + * that are currently on aFromLine. + */ +nsLineBox* NS_NewLineBox(nsIPresShell* aPresShell, nsLineBox* aFromLine, + nsIFrame* aFrame, PRInt32 aCount); class nsLineList; @@ -232,13 +244,14 @@ private: // Overloaded new operator. Uses an arena (which comes from the presShell) // to perform the allocation. void* operator new(size_t sz, nsIPresShell* aPresShell) CPP_THROW_NEW; - void operator delete(void* aPtr, size_t sz); + void operator delete(void* aPtr, size_t sz) MOZ_DELETE; public: - // Use these two functions to allocate and destroy line boxes + // Use these functions to allocate and destroy line boxes friend nsLineBox* NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame, - PRInt32 aCount, bool aIsBlock); - + bool aIsBlock); + friend nsLineBox* NS_NewLineBox(nsIPresShell* aPresShell, nsLineBox* aFromLine, + nsIFrame* aFrame, PRInt32 aCount); void Destroy(nsIPresShell* aPresShell); // mBlock bit @@ -284,7 +297,6 @@ public: // mImpactedByFloat bit void SetLineIsImpactedByFloat(bool aValue) { - NS_ASSERTION((false==aValue || true==aValue), "somebody is playing fast and loose with bools and bits!"); mFlags.mImpactedByFloat = aValue; } bool IsImpactedByFloat() const { @@ -293,7 +305,6 @@ public: // mLineWrapped bit void SetLineWrapped(bool aOn) { - NS_ASSERTION((false==aOn || true==aOn), "somebody is playing fast and loose with bools and bits!"); mFlags.mLineWrapped = aOn; } bool IsLineWrapped() const { @@ -302,7 +313,6 @@ public: // mInvalidateTextRuns bit void SetInvalidateTextRuns(bool aOn) { - NS_ASSERTION((false==aOn || true==aOn), "somebody is playing fast and loose with bools and bits!"); mFlags.mInvalidateTextRuns = aOn; } bool GetInvalidateTextRuns() const { @@ -344,20 +354,76 @@ public: return mFlags.mHadFloatPushed; } +private: + // Add a hash table for fast lookup when the line has more frames than this. + static const PRUint32 kMinChildCountForHashtable = 200; - // mChildCount value - PRInt32 GetChildCount() const { - return (PRInt32) mFlags.mChildCount; + /** + * Take ownership of aFromLine's hash table and remove the frames that + * stay on aFromLine from it, i.e. aFromLineNewCount frames starting with + * mFirstChild. This method is used to optimize moving a large number + * of frames from one line to the next. + */ + void StealHashTableFrom(nsLineBox* aFromLine, PRUint32 aFromLineNewCount); + + /** + * Does the equivalent of this->NoteFrameAdded and aFromLine->NoteFrameRemoved + * for each frame on this line, but in a optimized way. + */ + void NoteFramesMovedFrom(nsLineBox* aFromLine); + + void SwitchToHashtable() + { + MOZ_ASSERT(!mFlags.mHasHashedFrames); + PRUint32 count = GetChildCount(); + mFrames = new nsTHashtable< nsPtrHashKey >(); + mFlags.mHasHashedFrames = 1; + PRUint32 minSize = + NS_MAX(kMinChildCountForHashtable, PRUint32(PL_DHASH_MIN_SIZE)); + mFrames->Init(NS_MAX(count, minSize)); + for (nsIFrame* f = mFirstChild; count-- > 0; f = f->GetNextSibling()) { + mFrames->PutEntry(f); + } } - void SetChildCount(PRInt32 aNewCount) { - if (aNewCount < 0) { - NS_WARNING("negative child count"); - aNewCount = 0; + void SwitchToCounter() { + MOZ_ASSERT(mFlags.mHasHashedFrames); + PRUint32 count = GetChildCount(); + delete mFrames; + mFlags.mHasHashedFrames = 0; + mChildCount = count; + } + +public: + PRInt32 GetChildCount() const { + return NS_UNLIKELY(mFlags.mHasHashedFrames) ? mFrames->Count() : mChildCount; + } + + /** + * Register that aFrame is now on this line. + */ + void NoteFrameAdded(nsIFrame* aFrame) { + if (NS_UNLIKELY(mFlags.mHasHashedFrames)) { + mFrames->PutEntry(aFrame); + } else { + if (++mChildCount >= kMinChildCountForHashtable) { + SwitchToHashtable(); + } } - if (aNewCount > LINE_MAX_CHILD_COUNT) { - aNewCount = LINE_MAX_CHILD_COUNT; + } + + /** + * Register that aFrame is not on this line anymore. + */ + void NoteFrameRemoved(nsIFrame* aFrame) { + MOZ_ASSERT(GetChildCount() > 0); + if (NS_UNLIKELY(mFlags.mHasHashedFrames)) { + mFrames->RemoveEntry(aFrame); + if (mFrames->Count() < kMinChildCountForHashtable) { + SwitchToCounter(); + } + } else { + --mChildCount; } - mFlags.mChildCount = aNewCount; } // mBreakType value @@ -473,10 +539,13 @@ public: nsIFrame* LastChild() const; #endif +private: PRInt32 IndexOf(nsIFrame* aFrame) const; +public: bool Contains(nsIFrame* aFrame) const { - return IndexOf(aFrame) >= 0; + return NS_UNLIKELY(mFlags.mHasHashedFrames) ? mFrames->Contains(aFrame) + : IndexOf(aFrame) >= 0; } // whether the line box is "logically" empty (just like nsIFrame::IsEmpty) @@ -504,6 +573,12 @@ public: nsRect mBounds; + // mFlags.mHasHashedFrames says which one to use + union { + nsTHashtable< nsPtrHashKey >* mFrames; + PRUint32 mChildCount; + }; + struct FlagBits { PRUint32 mDirty : 1; PRUint32 mPreviousMarginDirty : 1; @@ -521,10 +596,8 @@ public: // Indicates that this line *may* have a placeholder for a float // that was pushed to a later column or page. PRUint32 mHadFloatPushed : 1; + PRUint32 mHasHashedFrames: 1; PRUint32 mBreakType : 4; - - // FIXME: Move this out of FlagBits - PRUint32 mChildCount; }; struct ExtraData {