Closed Bug 1367207 Opened 7 years ago Closed 7 years ago

ImageLoader::DropRequestsForFrame is a bit hashtable-lookup happy

Categories

(Core :: Layout, enhancement)

53 Branch
enhancement
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla55
Performance Impact high
Tracking Status
firefox55 --- fixed

People

(Reporter: bzbarsky, Assigned: MatsPalmgren_bugz)

References

Details

Attachments

(4 files, 4 obsolete files)

4.92 KB, patch
dholbert
: review+
Details | Diff | Splinter Review
3.19 KB, patch
froydnj
: review+
Details | Diff | Splinter Review
5.24 KB, patch
dholbert
: review+
Details | Diff | Splinter Review
2.58 KB, patch
MatsPalmgren_bugz
: review+
Details | Diff | Splinter Review
This shows up somewhat when destroying frames.  We do an unconditional lookup in mFrameToRequestMap which presumably usually misses.  If it _does_ hit, we proceed to do more lookups, and in fact repeat that same lookup (twice, if you count the ensuing Remove call).

Might be nice to gate this on a boolean flag or something.
Whiteboard: [qf]
Whiteboard: [qf] → [qf:p1]
Sure, this might be worth spending a frame bit on...
However, the more serious issue here seems to be that ImageLoader::DropRequestsForFrame
does one mFrameToRequestMap.Get and then, for *each* of the requests, calls
DisassociateRequestFromFrame which does *another* mFrameToRequestMap.Get.
http://searchfox.org/mozilla-central/rev/507743376d1ba753d14ab6b9305b7c6358570be8/layout/style/ImageLoader.cpp#153,182,192
Assignee: nobody → mats
Which issue is more serious depends on how many things have entries in the mFrameToRequestMap to start with.
mats: if you haven't gotten too far into this, I have a WIP patch that I spun up in the background over the past ~hour to avoid the redundant mFrameToRequestMap.Get call.  (I should've self-assigned -- wanted to spin up a partial patch to see what I was getting myself into first. :) It's maybe ~10 lines total.)

So if you aren't already mostly-done with the Get() redundancy-elimination, feel free to kick this in my direction.  I'm good either way. :)

(RE the frame-state bit, it looks like we have exactly one frame state bit available right now, and it's unclear that *this specific hashtable lookup* is how we should spend it. Having said that, maybe we should buy ourselves some more frame state bits.)
Flags: needinfo?(mats)
Attached patch strawman patch (obsolete) — Splinter Review
(For reference, here's the patch I've got, as a strawman option -- just splitting most of the existing public method into a private method, which takes a RequestSet (whose typedef is private, hence abstracting its usage away in a private helper).

This way, the internal callsite can pass in the already-obtained RequestSet*, and the external caller can use the public "wrapper", which now just looks up a RequestSet and passes that in to the private helper.

I'm happy to finish this off [probably just needs a commit message] or to discard it in favor of whatever likely-similar strategy you might be pursuing here. :)
I just pushed what I have to Try:
https://treeherder.mozilla.org/#/jobs?repo=try&revision=2e9aa4a6127807d28290e82bee1324810a653630
I think that's pretty much what I intended to do.

> RE the frame-state bit

I just used a bool:1 for this.  FYI, I have some other patches that'll shuffle around
some frame state bits to make more of them global.  I've made two of them global so far,
but I think there's potential for a few more after that.
(details in bug 1367209 comment 8)
Flags: needinfo?(mats)
Comment on attachment 8874598 [details] [diff] [review]
strawman patch

OK, nice! And: packing a new bool:1 alongside existing bool:1 seems reasonable to me, yeah.
Attachment #8874598 - Attachment is obsolete: true
BTW, part 2 fixes the extra hashtable lookup as discussed above, but also fixes
a couple of additional perf problems in DropRequestsForFrame:
It first makes a copy of the array and then removes each entry separately.
http://searchfox.org/mozilla-central/rev/507743376d1ba753d14ab6b9305b7c6358570be8/layout/style/ImageLoader.cpp#159,188
Both seems unnecessary since we're removing all entries and then
deleting the array here.
This is a pattern I've seen quite a lot recently when investigating
these hashtable perf issues:
1. Get()
2. do some stuff with the value
3. possibly Remove() the entry based on the new state of the value

It would be nice to have a way to do this without having to do
a second hashtable lookup in 3.

(See part 4 for a couple of examples.)
Attachment #8874872 - Flags: review?(nfroyd)
Comment on attachment 8874872 [details] [diff] [review]
part 3 - Introduce nsClassHashtable::LookupRemoveIf() for consumers that wants to do a Get() then inspect/modify the value then, maybe, Remove() the entry

Review of attachment 8874872 [details] [diff] [review]:
-----------------------------------------------------------------

r=me with the change below.  It's entirely possible that I'm tracing the calls and overload resolution wrong here, though.

I'm trying to think of a good way to test this that would have caught the below (and would ensure we don't regress things!), but I can't think of a way that doesn't require overhauling hashtables at the same time. :(

::: xpcom/ds/nsClassHashtable.h
@@ +140,5 @@
> +  /**
> +   * Looks up aKey in the hashtable and if found calls the given callback
> +   * aFunction with the value.  If the callback returns true then the entry
> +   * is removed.  If aKey doesn't exist nothing happens.
> +   * The hashtable must not be modified in the callback function.

Thank you for this documentation comment.

@@ +167,5 @@
> +    bool shouldRemove = aFunction(ent->mData);
> +    MOZ_ASSERT(tableGeneration == base_type::GetGeneration(),
> +               "hashtable was modified by the LookupRemoveIf callback!");
> +    if (shouldRemove) {
> +      this->Remove(aKey);

I think this should be RemoveEntry(ent).  Otherwise, Remove() will do a second hashtable lookup to find the entry for aKey, no?  (nsBaseHashtable::Remove(KeyType) will call nsTHashtable::RemoveEntry(KeyType), which sounds like it doesn't do an actual lookup, but nsTHashtable::RemoveEntry(KeyType) will call PLDHashTable::Remove, which very much does a lookup.)

Can you file a followup for renaming nsTHashtable::RemoveEntry(KeyType) or seeing if we can remove it entirely?  Might as well file a followup for changing RemoveAndForget to not do a redundant lookup as well.
Attachment #8874872 - Flags: review?(nfroyd) → review+
Bah, I looked at RemoveAndForget() and assumed it did the right thing, oh well...

We might as well fix that too while we're here since it's a trivial change.
Attachment #8874872 - Attachment is obsolete: true
Attachment #8874890 - Flags: review?(nfroyd)
Comment on attachment 8874890 [details] [diff] [review]
part 3 - Introduce nsClassHashtable::LookupRemoveIf() for consumers that wants to do a Get() then inspect/modify the value then, maybe, Remove() the entry

Review of attachment 8874890 [details] [diff] [review]:
-----------------------------------------------------------------

Thank you!
Attachment #8874890 - Flags: review?(nfroyd) → review+
> Can you file a followup for renaming nsTHashtable::RemoveEntry(KeyType) or seeing if we can remove it entirely?

Filed bug 1370573.
Comment on attachment 8874865 [details] [diff] [review]
part 1 - Add a nsIFrame bool:1 member to track whether the frame has some image requests associated with it

Review of attachment 8874865 [details] [diff] [review]:
-----------------------------------------------------------------

r=me
Attachment #8874865 - Flags: review?(dholbert) → review+
Blocks: 1370632
Comment on attachment 8874867 [details] [diff] [review]
part 2 - Update mFrameToRequestMap/mRequestToFrameMap more efficiently on frame removal

Review of attachment 8874867 [details] [diff] [review]:
-----------------------------------------------------------------

r=me, just a few nits (with the function-rename being the most important one)

::: layout/style/ImageLoader.cpp
@@ +136,5 @@
>  }
>  
>  void
> +ImageLoader::DisassociateRequestFromFrameSet(imgIRequest* aRequest,
> +                                             nsIFrame*    aFrame)

This function needs a different name, I think.  Right now it's hard to figure out (or recall) what the actual behavior is based on the name -- superficially, the naming implies that this function would sever a request<-->frameset relationship.  BUT, that's not what it does -- instead, it removes a single frame *from* the frameset that is associated with a given request.  I have a pretty hard time mapping the function-name onto that behavior.

(Also: "Disassociate" kind of implies that we're updating *both* maps, since the association is a bidirectional relationship. But this function only updates one map.)

Could you rename this to more clearly correspond to its behavior?  Maybe:
  RemoveFrameFromRequestFrameSet()
...or something like that?

@@ +169,5 @@
> +  MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
> +  DisassociateRequestFromFrameSet(aRequest, aFrame);
> +
> +  RequestSet* requestSet = nullptr;
> +  mFrameToRequestMap.Get(aFrame, &requestSet);

Now that this function has a bit less symmetry, it'd be nice to include two comments to reinforce the symmetry that does actually exist (to indicate that we're updating both maps)

e.g. right after the initial assertion:
  // Remove the frame from the request-to-FrameSet map:

...and then after that:
  // Remove the request from the frame-to-RequestSet map:

::: layout/style/ImageLoader.h
@@ +100,5 @@
>    nsresult OnImageIsAnimated(imgIRequest* aRequest);
>    nsresult OnFrameUpdate(imgIRequest* aRequest);
>  
> +  // Helper for DropRequestsForFrame / DisassociateRequestFromFrame above.
> +  void DisassociateRequestFromFrameSet(imgIRequest* aRequest, nsIFrame* aFrame);

Could you add an extra line here to briefly sum up what this function actually does? Something like:
  // Removes aFrame from aRequest's FrameSet (and drops the FrameSet if empty)

(And as noted for the .cpp file, I think this function wants a rename.)
Attachment #8874867 - Flags: review?(dholbert) → review+
It might actually be clearest to split up DisassociateRequestFromFrame's work into two helper functions:
  void RemoveRequestToFrameMapping(imgIRequest* aRequest, nsIFrame* aFrame); // Updates mRequestToFrameMap
  void RemoveFrameToRequestMapping(imgIRequest* aRequest, nsIFrame* aFrame); // Updates mFrameToRequestMap

(The former is what your "part 3" currently calls DisassociateRequestFromFrameSet -- and the latter would just encapsulate the rest of the code that you've currently got living in DisassociateRequestFromFrame)

Then, DisassociateRequestFromFrame would just be a two-liner:
{
  RemoveRequestToFrameMapping(aRequest, aFrame);
  RemoveFrameToRequestMapping(aRequest, aFrame);
}

That'd be nice from a symmetry/grokkability perspective.  And at the end of your patch stack, those two RemoveXXXFromYYY functions would basically look identical to each other -- each would call LookupRemoveIf on one or the other hash-map.  The only difference is which map they operate on, plus the fact that one function would have 1 caller and the other has 2 callers.
Flags: needinfo?(mats)
Comment on attachment 8874867 [details] [diff] [review]
part 2 - Update mFrameToRequestMap/mRequestToFrameMap more efficiently on frame removal

Review of attachment 8874867 [details] [diff] [review]:
-----------------------------------------------------------------

[retroactively calling part 2 "r-" since I think it can be structured a bit more clearly, per comment 19, such that my suggestions in comment 18 mostly become obsolete.]
Attachment #8874867 - Flags: review+ → review-
Comment on attachment 8874873 [details] [diff] [review]
part 4 - Use LookupRemoveIf() to avoid a second hashtable lookup for Remove()

Review of attachment 8874873 [details] [diff] [review]:
-----------------------------------------------------------------

r=me on part 4.

(Though, as noted in comment 19, I'd like for the code you're rewriting here to live inside of two symmetric functions named something like RemoveRequestToFrameMapping & RemoveFrameToRequestMapping.  So I'm hoping the contextual code will end up looking slightly different in the end here. But the rewrite itself looks good, so r+.)
Attachment #8874873 - Flags: review?(dholbert) → review+
(compiles locally, but not tested on Try yet since it appears we have infra problems ATM)
Flags: needinfo?(mats)
Attachment #8875022 - Flags: review?(dholbert)
(just updating conflicts from the changes, carrying forward r+)
Attachment #8874873 - Attachment is obsolete: true
Attachment #8875024 - Flags: review+
Comment on attachment 8875022 [details] [diff] [review]
part 2 - Update mFrameToRequestMap/mRequestToFrameMap more efficiently on frame removal

Review of attachment 8875022 [details] [diff] [review]:
-----------------------------------------------------------------

Thanks for updating this! r=me with one nit:

::: layout/style/ImageLoader.h
@@ +101,5 @@
>    nsresult OnFrameUpdate(imgIRequest* aRequest);
>  
> +  // Helper for DropRequestsForFrame / DisassociateRequestFromFrame above.
> +  void RemoveRequestToFrameMapping(imgIRequest* aRequest, nsIFrame* aFrame);
> +  void RemoveFrameToRequestMapping(imgIRequest* aRequest, nsIFrame* aFrame);

s/Helper/Helpers/ in the comment here (now that there are two functions)
Attachment #8875022 - Flags: review?(dholbert) → review+
Comment on attachment 8875022 [details] [diff] [review]
part 2 - Update mFrameToRequestMap/mRequestToFrameMap more efficiently on frame removal

Review of attachment 8875022 [details] [diff] [review]:
-----------------------------------------------------------------

::: layout/style/ImageLoader.cpp
@@ +144,5 @@
>      nsCOMPtr<imgINotificationObserver> observer;
>      aRequest->GetNotificationObserver(getter_AddRefs(observer));
>      MOZ_ASSERT(!observer || observer == this);
>    }
>  #endif

Oh, actually -- one more observation/request -- could you add one more patch that moves this #ifdef DEBUG chunk (with GetNotificationObserver etc) back to the DisassociateRequestFromFrame() implementation?  rs=me on that patch in advance.

It's the only non-symmetrical think between the two RemoveXXXFromYYY cleanup functions at this point.  (And it feels a bit odd/arbitrary there, when these functions are otherwise equivalent, and when the assertion should be equally-valid in each of them.)  It makes more sense to me in DisassociateRequestFromFrame, since that's a public API and requires a higher level of parameter-sanity-checking than these private helper-functions do.
Attachment #8874867 - Attachment is obsolete: true
(In reply to Daniel Holbert [:dholbert] from comment #25)
> Oh, actually -- one more observation/request -- could you add one more patch
> that moves this #ifdef DEBUG chunk (with GetNotificationObserver etc) back
> to the DisassociateRequestFromFrame() implementation?

It checks that each of the request objects in the calls from
DropRequestsForFrame() are OK too, which seems like a good thing.
So I think it should stay.
True -- though the request objects in DropRequestsForFrame are less suspect (with regards to this assertion about "is this request related to |this| ImageLoader"), since in that function, we're pulling the request objects out of a member variable on the ImageLoader. *shrug*

Mostly, it feels a little odd to have this the assertion in one RemoveXXXFromYYY function but not in the other -- it gives the impression that we have different expectations in the two functions.

Anyway, not a big deal -- the patches are good as they are currently, too (modulo the comment 24 typo).
I'm keeping the assertions as the currently are.  I don't want to
pile on unrelated changes in this bug.
Pushed by mpalmgren@mozilla.com:
https://hg.mozilla.org/integration/mozilla-inbound/rev/67181c32580e
part 1 - Add a nsIFrame bool:1 member to track whether the frame has some image requests associated with it.  Skip the call to DropRequestsForFrame() on frame destruction if the bit isn't set.  r=dholbert
https://hg.mozilla.org/integration/mozilla-inbound/rev/6d87a5768954
part 2 - Update mFrameToRequestMap/mRequestToFrameMap more efficiently on frame removal.  r=dholbert
https://hg.mozilla.org/integration/mozilla-inbound/rev/7307d037a922
part 3 - Introduce nsClassHashtable::LookupRemoveIf() for consumers that wants to do a Get() then inspect/modify the value then, maybe, Remove() the entry.  This is to avoid a second hashtable lookup for the Remove().  r=froydnj
https://hg.mozilla.org/integration/mozilla-inbound/rev/7fc0cbe0d71c
part 4 - Use LookupRemoveIf() to avoid a second hashtable lookup for Remove().  r=dholbert
Depends on: 1377910
Performance Impact: --- → P1
Whiteboard: [qf:p1]
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: