Closed
Bug 1362886
Opened 7 years ago
Closed 7 years ago
Consider devirtualizing nsIFrame::IsLeaf()
Categories
(Core :: Layout, enhancement)
Core
Layout
Tracking
()
RESOLVED
FIXED
mozilla55
Tracking | Status | |
---|---|---|
firefox55 | --- | fixed |
People
(Reporter: ehsan.akhgari, Assigned: MatsPalmgren_bugz)
References
Details
Attachments
(3 files, 7 obsolete files)
2.52 KB,
text/plain
|
Details | |
15.15 KB,
patch
|
jfkthame
:
review+
|
Details | Diff | Splinter Review |
21.55 KB,
patch
|
jfkthame
:
review+
|
Details | Diff | Splinter Review |
I noticed this come up in the profile of bug 944127. As I was trying to come up with a strategy to fix this, I noticed that there are only two implementations of this that aren't simply returning true: * nsContainerFrame::IsLeaf() returns false, but I'm not sure how important that is, since so concrete frames of type nsContainerFrame should ever be created, as far as I know. * nsMenuControlFrame::IsLeaf()'s implementation is very complex: <https://searchfox.org/mozilla-central/rev/8b70b0a5038ef9472fe7c53e04a70c978bb06aed/layout/xul/nsMenuPopupFrame.cpp#412>. It seems like we can replace IsLeaf() with an inline function that checks mType against LayoutFrameType::MenuPopup and calls an nsMenuControlFrame helper in that case.
Reporter | ||
Comment 1•7 years ago
|
||
The existing implementations of IsLeaf() all return true except for two: * nsContainerFrame::IsLeaf() returns false, but no concrete frame types of this type can be created. * nsMenuControlFrame::IsLeaf()'s implementation is complex. This patch makes nsIFrame an inline non-virtual function that hands off the hard work to nsMenuControlFrame through a virtual function if the frame type at hand is a XUL menu control frame, and otherwise merely returns true.
Attachment #8865263 -
Flags: review?(dbaron)
Reporter | ||
Updated•7 years ago
|
Assignee: nobody → ehsan
Assignee | ||
Comment 2•7 years ago
|
||
(In reply to :Ehsan Akhgari (super long backlog, slow to respond) from comment #1) > * nsContainerFrame::IsLeaf() returns false, but no concrete frame types of > this type can be created. I don't understand why that matters. nsBlockFrame is a sub-class that we do create concrete instances of, so wouldn't this patch make nsBlockFrames return true now? (and Grid/Flexbox containers, various table frames etc) Did this patch pass regression tests?
Flags: needinfo?(ehsan)
Reporter | ||
Comment 3•7 years ago
|
||
Comment on attachment 8865263 [details] [diff] [review] Devirtualize nsIFrame::IsLeaf() Oh of course not! Now that we have FrameTypeList, we can add a field to each frame tupe to indicate whether it is leaf or not, and have a third value for the menu popup frame case. Then we can have a table of these values and have IsLeaf() look at that table to decide what to do. Sorry about not being careful enough...
Attachment #8865263 -
Attachment is obsolete: true
Flags: needinfo?(ehsan)
Attachment #8865263 -
Flags: review?(dbaron)
Assignee | ||
Comment 4•7 years ago
|
||
I think an array of bools should work fine for the non-MenuPopup types. You need to add a couple of new FrameTypes for this though since that hierarchy doesn't correspond exactly to the IsLeaf results we want. Let me know if you want help with that...
Reporter | ||
Comment 5•7 years ago
|
||
Reporter | ||
Comment 6•7 years ago
|
||
(In reply to Mats Palmgren (:mats) from comment #4) > I think an array of bools should work fine for the non-MenuPopup types. > You need to add a couple of new FrameTypes for this though since that > hierarchy doesn't correspond exactly to the IsLeaf results we want. > Let me know if you want help with that... The WIP patch I just attached sort of does the array of bool thing. The try server isn't very happy: <https://treeherder.mozilla.org/#/jobs?repo=try&revision=c83c16f121133b9c6880bdfee42bfdb32f16dab0> I suspect that's because of the issue you had in mind. I do in fact appreciate some help here if you don't mind. :-) I didn't really expect this to turn into this much work. I do think it's worth finishing it but I don't have a sense of how much work adding the new frame types is going to be, if you think it's a lot of work then maybe it's not worth investing a lot of time in it right now?
Reporter | ||
Updated•7 years ago
|
Assignee: ehsan → nobody
Assignee | ||
Comment 7•7 years ago
|
||
Your patch looks great so far. I think it just needs a few tweaks to make it work so it seems worth doing to push it over the line...
Assignee: nobody → mats
Reporter | ||
Comment 8•7 years ago
|
||
Thank you so much! :-)
Assignee | ||
Comment 9•7 years ago
|
||
Here's a patch that I wrote on top of your's. I'll comment on my changes in logical order, i.e. not necessarily the order they occur in the patch. >+FRAME_TYPE(Container, NotLeaf) So, we have some sub-classes that wants to be Leaf (see the removed IsLeaf() methods) and some that don't. The default for nsContainerFrame sub-classes needs to be NotLeaf b/c that's what nsContainerFrame::IsLeaf() currently says. >+FRAME_TYPE(AtomicContainer, Leaf) A sub-class of nsContainerFrame that obviously should be Leaf, as its current IsLeaf() method says. >+FRAME_TYPE(FileControl, Leaf) A sub-class of nsBlockFrame, I'll come back to this one later... >+FRAME_TYPE(MathMLmspace, Leaf) A sub-class of nsMathMLContainerFrame, a sub-class of nsContainerFrame, but is a leaf per its current IsLeaf() method. >-FRAME_TYPE(None, NotLeaf) >+FRAME_TYPE(None, Leaf) None represents the "anything-else" case, i.e. should return what nsIFrame::IsLeaf() says, i.e. Leaf. >-FRAME_TYPE(Progress, NotLeaf) >+FRAME_TYPE(Progress, Leaf) >-FRAME_TYPE(Range, NotLeaf) >+FRAME_TYPE(Range, Leaf) >-FRAME_TYPE(SVGUse, NotLeaf) >+FRAME_TYPE(SVGUse, Leaf) >-FRAME_TYPE(TextInput, NotLeaf) >+FRAME_TYPE(TextInput, Leaf) All these inherit nsContainerFrame, but needs to be Leaf according to their current IsLeaf() methods. >-FRAME_TYPE(TableCol, NotLeaf) >+FRAME_TYPE(TableCol, Leaf) Just a mistake - this class inherits nsFrame so is a leaf. >+ using FT = mozilla::LayoutFrameType; >+ nsAtomicContainerFrame(nsStyleContext* aContext, FT aType) >+ : nsContainerFrame(aContext, aType != FT::None ? aType : FT::AtomicContainer) To support the FRAME_TYPE added above. >+ using FT = mozilla::LayoutFrameType; >+ nsContainerFrame(nsStyleContext* aContext, FT aType) >+ : nsSplittableFrame(aContext, aType != FT::None ? aType : FT::Container) ditto >+ explicit nsMathMLmspaceFrame(nsStyleContext* aContext) >+ : nsMathMLContainerFrame(aContext, mozilla::LayoutFrameType::MathMLmspace) {} ditto >+ using FT = mozilla::LayoutFrameType; >+ nsMathMLContainerFrame(nsStyleContext* aContext, FT aType = FT::None) >+ : nsContainerFrame(aContext, aType) This is needed for MathMLmspace which now pass up aType. > nsFileControlFrame::nsFileControlFrame(nsStyleContext* aContext) >- : nsBlockFrame(aContext) >+ : nsBlockFrame(aContext, mozilla::LayoutFrameType::FileControl) nsFileControlFrame is interesting b/c it inherits nsBlockFrame but (currently) have no LayoutFrameType of its own. It is a leaf though, per its IsLeaf() method. So we need to add a FRAME_TYPE for it to be able to mark it as Leaf (unlike nsBlockFrame), but this changes its Type() result but this *might* cause problems. Based on how LayoutFrameType::Block is used though, it looks OK to me. http://searchfox.org/mozilla-central/search?q=LayoutFrameType%3A%3ABlock&case=false®exp=false&path= BTW, here's a Try run with these changes. I also kept the current IsLeaf() methods (renamed as IsLeafOLD()) and verify that the new value is the same as before: > MOZ_ASSERT(sLeafTypes[size_t(mType)] == IsLeafOLD()) https://treeherder.mozilla.org/#/jobs?repo=try&revision=1198216fb1ad19e0387270ebcb411c231c0ca308
Assignee | ||
Comment 10•7 years ago
|
||
BUT, after making these changes (nsFileControlFrame in particular) I think that mapping Leaf/NotLeaf from the FrameType hierarchy isn't a good match. It seems brittle and hard to maintain. Also, I think we should probably move away from using LayoutFrameType in general. So, I'll try a different approach that I think may be easier to deal with and may have other benefits as well...
Reporter | ||
Comment 11•7 years ago
|
||
Ouch, sorry to have lead you down this path, I admit that I didn't think this through all that carefully in hindsight... :-/
Assignee | ||
Comment 12•7 years ago
|
||
No worries, Ehsan. I've figured out a better way to devirtualize all these methods, Type(), IsLeaf(), IsFrameOfType(), do_QueryFrame(), etc, that I think should be easier to understand and maintain. I've filed bug 1364805 to implement that. I'll then devirtualize IsLeaf() here using that scheme.
Assignee | ||
Comment 13•7 years ago
|
||
Attachment #8865708 -
Attachment is obsolete: true
Attachment #8866091 -
Attachment is obsolete: true
Attachment #8868694 -
Flags: review?(jfkthame)
Assignee | ||
Comment 14•7 years ago
|
||
Attachment #8868699 -
Flags: review?(jfkthame)
Assignee | ||
Comment 15•7 years ago
|
||
Fwiw, I did a Try run with both the old IsLeaf() methods and the new <bitset> that asserted that the values matched: https://treeherder.mozilla.org/#/jobs?repo=try&revision=ce762cd03436a773eec41845e13f5347c3f2612f
Comment 16•7 years ago
|
||
Is it really worth creating a bitset here, rather than just an array of bool? I'd assume the latter would be (marginally) cheaper to access.
Assignee | ||
Comment 17•7 years ago
|
||
(In reply to Jonathan Kew (:jfkthame) from comment #16) > Is it really worth creating a bitset here, rather than just an array of > bool? I'd assume the latter would be (marginally) cheaper to access. Good question, dbaron asked about virtual calls vs array lookup too, in bug 1364805. It's a bit hard to measure though since a single IsLeaf() call is in the nano-second range so timing each individual call using TimeStamp might not be accurate. OTOH, doing a mockup that does millions of "IsLeaf()" calls in a loop is easier to time, but likely not representative of a real workload since there will be no cache misses. Anyway, I tried both, and it seems like array lookup wins in most cases. It looks like a clear win when replacing a virtual call with a simple array lookup, i.e. if IsLeaf had been: IsLeaf() { return array[mClass]; }. But IsLeaf() actually involves two array lookups and a branch: bool IsLeaf() const { if (Type() == mozilla::LayoutFrameType::MenuPopup) { return IsXULMenuPopupFrameLeaf(); } MOZ_ASSERT(uint8_t(mClass) < mozilla::ArrayLength(sIsLeafArray)); return sIsLeafArray[uint8_t(mClass)]; } Note that the Type() call there is another array[mClass] lookup. On Linux, virtual calls are actually faster than that when run in a tight loop, but note that this is the ideal case with no I-cache misses. On OSX and Win64 though, the above still wins over virtual calls. Same platform difference for bitset: it (barely) wins on Linux, but not on OSX/Win64. BTW, here's my mockup test: https://hg.mozilla.org/try/rev/35dc41cf72fd08bb154a5bc6d3fdc0929826d18c (I hacked the TestTimeStamp gtest to instead do my tests) Better ideas for more accurate measuring are welcome...
Assignee | ||
Comment 18•7 years ago
|
||
(now using array instead of bitset)
Attachment #8868694 -
Attachment is obsolete: true
Attachment #8868694 -
Flags: review?(jfkthame)
Attachment #8870886 -
Flags: review?(jfkthame)
Assignee | ||
Comment 19•7 years ago
|
||
Attachment #8868699 -
Attachment is obsolete: true
Attachment #8868699 -
Flags: review?(jfkthame)
Attachment #8870887 -
Flags: review?(jfkthame)
Assignee | ||
Comment 20•7 years ago
|
||
Ehsan, feel free to test the Try builds with your workload to see if it makes a difference: https://treeherder.mozilla.org/#/jobs?repo=try&revision=52e9a21c630a84a98056d1504dbcf1c7050ce307
Comment 21•7 years ago
|
||
Comment on attachment 8870887 [details] [diff] [review] part 2 - Devirtualize the IsLeaf() method by doing an array lookup instead. Review of attachment 8870887 [details] [diff] [review]: ----------------------------------------------------------------- ::: layout/generic/nsIFrame.h @@ +2844,5 @@ > + if (Type() == mozilla::LayoutFrameType::MenuPopup) { > + return IsXULMenuPopupFrameLeaf(); > + } > + MOZ_ASSERT(uint8_t(mClass) < mozilla::ArrayLength(sIsLeafArray)); > + return sIsLeafArray[uint8_t(mClass)]; It seems unfortunate that (in the common case) we end up doing two global array lookups for IsLeaf(), first one to look up Type() and then the second one here. So here's a thought: how about instead of an array of bool, make the sIsLeafArray into an array of something like enum class EIsLeaf : uint8_t { kNotLeaf, kLeaf, kUnknown }; and then do bool IsLeaf() const { EIsLeaf isLeaf = sIsLeafArray[mClass]; if (isLeaf == EIsLeaf::kUnknown) { return IsLeafDynamic(); // virtual, override in menupopup } else { return isLeaf == EIsLeaf::kLeaf; } } (with added casts, assertions, etc as appropriate) so that we only need a single array lookup. WDYT?
Assignee | ||
Comment 22•7 years ago
|
||
Good point, that's definitely better. The only change in nsFrameIdList.h compared to the last patch is that nsMenuPopupFrame now uses DynamicLeaf. I'm making it intentionally a bit more generic than what is needed for the IsLeaf state, so that we can add more state bits to this array in the future if needed.
Attachment #8870886 -
Attachment is obsolete: true
Attachment #8870886 -
Flags: review?(jfkthame)
Attachment #8871406 -
Flags: review?(jfkthame)
Assignee | ||
Comment 23•7 years ago
|
||
Attachment #8870887 -
Attachment is obsolete: true
Attachment #8870887 -
Flags: review?(jfkthame)
Attachment #8871409 -
Flags: review?(jfkthame)
Updated•7 years ago
|
Attachment #8871406 -
Flags: review?(jfkthame) → review+
Comment 24•7 years ago
|
||
Comment on attachment 8871409 [details] [diff] [review] part 2 - Devirtualize the IsLeaf() method by doing an array lookup instead. Review of attachment 8871409 [details] [diff] [review]: ----------------------------------------------------------------- Looks good, thanks!
Attachment #8871409 -
Flags: review?(jfkthame) → review+
Comment 25•7 years ago
|
||
Pushed by mpalmgren@mozilla.com: https://hg.mozilla.org/integration/mozilla-inbound/rev/13074ff6822b part 1 - Annotate the FRAME_ID for each concrete frame class whether it's a leaf or not. Generate an array containing the IsLeaf state for each frame class. r=jfkthame https://hg.mozilla.org/integration/mozilla-inbound/rev/d904a186e908 part 2 - Devirtualize the IsLeaf() method by doing an array lookup instead. r=jfkthame
Comment 26•7 years ago
|
||
bugherder |
https://hg.mozilla.org/mozilla-central/rev/13074ff6822b https://hg.mozilla.org/mozilla-central/rev/d904a186e908
Status: NEW → RESOLVED
Closed: 7 years ago
status-firefox55:
--- → fixed
Resolution: --- → FIXED
Target Milestone: --- → mozilla55
You need to log in
before you can comment on or make changes to this bug.
Description
•