Bug 238654 - devirtualize EventTarget::{ref,deref,eventTargetData}
Summary: devirtualize EventTarget::{ref,deref,eventTargetData}
Status: RESOLVED CONFIGURATION CHANGED
Alias: None
Product: WebKit
Classification: Unclassified
Component: DOM (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Cameron McCormack (:heycam)
URL:
Keywords: InRadar
: 241542 (view as bug list)
Depends on:
Blocks:
 
Reported: 2022-03-31 23:28 PDT by Cameron McCormack (:heycam)
Modified: 2023-05-29 19:55 PDT (History)
8 users (show)

See Also:


Attachments
Patch (15.87 KB, patch)
2022-03-31 23:31 PDT, Cameron McCormack (:heycam)
no flags Details | Formatted Diff | Diff
Patch (15.94 KB, patch)
2022-03-31 23:32 PDT, Cameron McCormack (:heycam)
no flags Details | Formatted Diff | Diff
Patch (17.42 KB, patch)
2022-04-01 20:37 PDT, Cameron McCormack (:heycam)
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (v2) (19.88 KB, patch)
2022-04-03 00:21 PDT, Cameron McCormack (:heycam)
no flags Details | Formatted Diff | Diff
Patch (v2) (21.17 KB, patch)
2022-04-03 00:24 PDT, Cameron McCormack (:heycam)
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (v3.1) (18.52 KB, patch)
2022-06-12 15:03 PDT, Cameron McCormack (:heycam)
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Cameron McCormack (:heycam) 2022-03-31 23:28:13 PDT
These functions are called very often during Speedometer 2.  Most EventTargets are Nodes, so we can avoid virtual calls (and inline a bit more) by being able to know whether they are Nodes.  We can do that by moving the Node::m_refCountAndParentSet field up to EventTarget and using that to tell whether the object is probably a Node.  I'm seeing a ~0.4% improvement on Apple Silicon for the benchmark with this.
Comment 1 Radar WebKit Bug Importer 2022-03-31 23:28:57 PDT
<rdar://problem/91147450>
Comment 2 Cameron McCormack (:heycam) 2022-03-31 23:31:38 PDT
Created attachment 456326 [details]
Patch
Comment 3 Cameron McCormack (:heycam) 2022-03-31 23:32:35 PDT
Created attachment 456327 [details]
Patch
Comment 4 Cameron McCormack (:heycam) 2022-04-01 20:37:34 PDT
Created attachment 456435 [details]
Patch
Comment 5 Chris Dumez 2022-04-01 20:45:46 PDT
Comment on attachment 456435 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=456435&action=review

> Source/WebCore/dom/EventTarget.h:146
> +    mutable uint32_t m_nodeRefCountAndParentBit;

It is really not pretty to have a Node-specific data member on EventTarget. Not only is it weird encapsulation but it also makes every subclass object that is not a Node unnecessarily larger.

Maybe an alternative would be to have a boolean data member indicating that this EventTarget is a Node and then use downcast<Node>(*this).ref() / deref() ? It would look less odd IMHO.
Comment 6 Cameron McCormack (:heycam) 2022-04-01 21:16:06 PDT
(In reply to Chris Dumez from comment #5)
> Maybe an alternative would be to have a boolean data member indicating that
> this EventTarget is a Node and then use downcast<Node>(*this).ref() /
> deref() ? It would look less odd IMHO.

This also makes non-Node EventTargets larger, although adding just a boolean does mean that there is some opportunity for non-Node EventTargets to pack some of their own data up against the boolean.  Node doesn't have any 1 or 2 byte members to pack in there, though, so this would make all Nodes bigger.  I think we want to avoid that.

Another alternative could be instead to move the flags word up to EventTarget.  Only one flag bit would be relevant for EventTarget, and others would be relevant for Node and descendants.  Similar to how NodeFlags has Element etc. specific flags on it.

Would that be acceptable?
Comment 7 Chris Dumez 2022-04-01 21:31:35 PDT
(In reply to Cameron McCormack (:heycam) from comment #6)
> (In reply to Chris Dumez from comment #5)
> > Maybe an alternative would be to have a boolean data member indicating that
> > this EventTarget is a Node and then use downcast<Node>(*this).ref() /
> > deref() ? It would look less odd IMHO.
> 
> This also makes non-Node EventTargets larger, although adding just a boolean
> does mean that there is some opportunity for non-Node EventTargets to pack
> some of their own data up against the boolean.  Node doesn't have any 1 or 2
> byte members to pack in there, though, so this would make all Nodes bigger. 
> I think we want to avoid that.
> 
> Another alternative could be instead to move the flags word up to
> EventTarget.  Only one flag bit would be relevant for EventTarget, and
> others would be relevant for Node and descendants.  Similar to how NodeFlags
> has Element etc. specific flags on it.
> 
> Would that be acceptable?

Hmm, I don't think moving the node flags up to EventTarget is any better factoring than moving the NodeRefCount. In my opinion, It is just too confusing to have Node-specific data members on EventTarget.

It is true that increasing the size of Node would be unfortunate. That said, we did so recently when we started subclassing CanMakeWeakPtr<> and no-one noticed.

For now, I do not have a better proposal than the one I made but I cc'd a couple of people who might. A 0.4% progression is nice but we have to weight it against the how bad it makes the code look.
Comment 8 Cameron McCormack (:heycam) 2022-04-02 00:54:44 PDT
(In reply to Chris Dumez from comment #7)
> Hmm, I don't think moving the node flags up to EventTarget is any better
> factoring than moving the NodeRefCount. In my opinion, It is just too
> confusing to have Node-specific data members on EventTarget.

I don't want to belabor the point too much, but that's kind of the situation that NodeFlag is already in.  It has a bunch of flags relevant to all Nodes, but also ones that are only relevant to subclasses.  Maybe conceptually the distance between EventTarget and Node is greater than between Node and {Element,Text,DocumentFragment}, which makes it seem worse.

FWIW if the flags word lived on EventTarget, we could move EventTargetData::isFiringEventListeners there, so there would be two flags relevant to all EventTargets.  That would also shrink all non-Node EventTargets (they all inherit from EventTargetWithInlineData).

> It is true that increasing the size of Node would be unfortunate. That said,
> we did so recently when we started subclassing CanMakeWeakPtr<> and no-one
> noticed.

If it's truly not an issue to grow all EventTargets by another word, then I will go with that.  Interested to hear others' opinions too.
Comment 9 Yusuke Suzuki 2022-04-02 02:04:10 PDT
(In reply to Cameron McCormack (:heycam) from comment #8)
> (In reply to Chris Dumez from comment #7)
> > Hmm, I don't think moving the node flags up to EventTarget is any better
> > factoring than moving the NodeRefCount. In my opinion, It is just too
> > confusing to have Node-specific data members on EventTarget.
> 
> I don't want to belabor the point too much, but that's kind of the situation
> that NodeFlag is already in.  It has a bunch of flags relevant to all Nodes,
> but also ones that are only relevant to subclasses.  Maybe conceptually the
> distance between EventTarget and Node is greater than between Node and
> {Element,Text,DocumentFragment}, which makes it seem worse.
> 
> FWIW if the flags word lived on EventTarget, we could move
> EventTargetData::isFiringEventListeners there, so there would be two flags
> relevant to all EventTargets.  That would also shrink all non-Node
> EventTargets (they all inherit from EventTargetWithInlineData).
> 
> > It is true that increasing the size of Node would be unfortunate. That said,
> > we did so recently when we started subclassing CanMakeWeakPtr<> and no-one
> > noticed.
> 
> If it's truly not an issue to grow all EventTargets by another word, then I
> will go with that.  Interested to hear others' opinions too.

I think we should avoid increasing the size. WeakPtr one can fix a lot of bugs, but in this case, benefit is readability.
I think reducing these object size and keeping them small are very important and worth doing.
Comment 10 Geoffrey Garen 2022-04-02 08:45:46 PDT
Comment on attachment 456435 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=456435&action=review

>> Source/WebCore/dom/EventTarget.h:146
>> +    mutable uint32_t m_nodeRefCountAndParentBit;
> 
> It is really not pretty to have a Node-specific data member on EventTarget. Not only is it weird encapsulation but it also makes every subclass object that is not a Node unnecessarily larger.
> 
> Maybe an alternative would be to have a boolean data member indicating that this EventTarget is a Node and then use downcast<Node>(*this).ref() / deref() ? It would look less odd IMHO.

Especially given the existing complexity of node refcounting, I agree with the goal of retaining encapsulation. Checking a bit and down casting sounds good to me. That's still pretty well encapsulated. The only thing that leaks out is knowing that we have a subclass, but not any of its implementation details.
Comment 11 Cameron McCormack (:heycam) 2022-04-03 00:21:03 PDT
Created attachment 456501 [details]
Patch (v2)

Here's a version that moves the flags word up to EventTarget, but keeps all of the Node-specific flag definitions on Node.  I like this a lot more than the previous patch.
Comment 12 Cameron McCormack (:heycam) 2022-04-03 00:24:43 PDT
Created attachment 456502 [details]
Patch (v2)
Comment 13 Geoffrey Garen 2022-04-04 13:53:07 PDT
I thought of a way to put an "isNode" bit in EventTarget, without needing to move all the other bits for space.

1. EventTarget removes its CanMakeWeakPtr base class

2. EventTarget adds a WeakPtrFactory data member and re-exports the createWeakPtr() function

3. WeakPtrFactory uses PackedRefPtr<T> or CompactPointerTuple or whatever to make the low bits of its RefPtr usable, and offers an interface for getting/setting those bits, and EventTarget uses one bit to store the "isNode" state.
Comment 14 Cameron McCormack (:heycam) 2022-04-04 14:27:44 PDT
That sounds good.  I will probably still want to cram those three bits my v2 patch adds in there.  But we avoid the funky "reinterpret EventTargetFlags and NodeFlags".
Comment 15 Cameron McCormack (:heycam) 2022-06-12 15:03:51 PDT
Created attachment 460191 [details]
Patch (v3.1)

This is the latest refinement of the patch I tried.  But it didn't seem to help much.
Comment 16 Darin Adler 2022-06-12 16:04:44 PDT
*** Bug 241542 has been marked as a duplicate of this bug. ***
Comment 17 Darin Adler 2022-06-12 22:04:51 PDT
I started a new A/B test of Speedometer 2 with a rebased/tweaked version of Cameron’s latest attempt.
Comment 18 Cameron McCormack (:heycam) 2023-05-29 19:55:52 PDT
Devirtualizing ref() and deref() was done in bug 254113.

Devirtualizing eventTargetData() was done in bug 244710.