-
-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
selectors: Check the bloom filter at most once per complex selector. #15890
Conversation
r? @SimonSapin |
Also, cc @bholley |
@bors-servo try |
selectors: Check the bloom filter at most once per complex selector. Fixes servo/rust-selectors#107 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15890) <!-- Reviewable:end -->
💔 Test failed - linux-rel-css |
That one is an intermittent it seems. |
(Though @bzbarsky could be a good reviewer for this one too, actually). Which remembers we should probably limit the amount of selectors we test against the filter somehow, IIRC the gecko limit was four or something like that. Still I'd want to land this relatively soon. |
selector = &**cs; | ||
continue; | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still I haven't got all the details, but look like this prevents checking the first selector(the rightmost one) in the bloom filter. Maybe move this match to the end of the loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's intended. The bloom filter contains only data from the ancestors, which are the expensive selectors to check. It's quite tricky, I admit.
@@ -466,7 +452,10 @@ fn matches_simple_selector<E>( | |||
} | |||
SimpleSelector::Negation(ref negated) => { | |||
!negated.iter().all(|s| { | |||
matches_complex_selector(s, element, parent_bf, relations, flags) | |||
match matches_complex_selector_internal(s, element, relations, flags) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might have descendant combinators inside the :not(...)
. Don't we want to be able to use the ancestor filter to quickly return false for them too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not a super-clear win, and I wanted to prevent the filter from being misused, but I guess I can do that too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I meant that because :not
is already quite expensive, but sure, will do that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@bors-servo try |
selectors: Check the bloom filter at most once per complex selector. Fixes servo/rust-selectors#107 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15890) <!-- Reviewable:end -->
Hmm, I think I made a mistake. The selector inside |
So, servo implements |
OK, sounds good, thanks! |
@bors-servo r=heycam |
📌 Commit 2772419 has been approved by |
selectors: Check the bloom filter at most once per complex selector. Fixes servo/rust-selectors#107 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15890) <!-- Reviewable:end -->
💔 Test failed - linux-rel-wpt |
@bors-servo retry |
⚡ Previous build results for android, arm32, arm64, linux-dev, linux-rel-css, mac-dev-unit, mac-rel-css, mac-rel-wpt1, mac-rel-wpt2, windows-msvc-dev are reusable. Rebuilding only linux-rel-wpt... |
☀️ Test successful - android, arm32, arm64, linux-dev, linux-rel-css, linux-rel-wpt, mac-dev-unit, mac-rel-css, mac-rel-wpt1, mac-rel-wpt2, windows-msvc-dev |
The Gecko limit is 4 because gecko does the following:
Given that, we needed to have some fixed length we picked, and 4 was a compromise between bloating the data structure too much and having enough slots to cover common selectors reasonably. |
Hm, does that setup predate the existing of precomputed hashes in nsIAtom? |
No. In fact, I implemented precomputed hashes in nsIAtom as a prereq for the Bloom filter in Gecko, precisely so we could build the cascade faster, instead of having to hash even then. But even just getting to the atoms still involves a bunch of linked-list walking and whatnot in Gecko. I seem to recall that I tried measuring just looking for them every time, and it was slower than the precached approach, with lots of cache misses and whatnot. It might be worth remeasuring various stuff around this, in general. |
In Servo we have a Vec, so the only cache miss will be on the nsIAtoms themselves. Hopefully that's acceptable. |
No, you have a linked list of Vecs. Specifically, a ComplexSelector is a Vec of SimpleSelectors. But ComplexSelectors form a linked list. So given a selector like "#foo.bar > *" you have a linked list with two elements. The first element has probably an empty Vec or so, depending on default namespaces; the second element has a Vec with two items (one for the id selector, one for the class selector). Any case in which the Bloom filter helps at all will involve a linked list of length more than 1, and hence pointer-chasing. |
Oh, and instead of hoping we should obviously measure. What the priority of that measurement should be is hard to say. |
Oh interesting - is there a reason it actually needs to be a linked list rather than a Vec of Vecs? That would certainly make #16019 easier. |
I have no idea. I'm still in the "butterfly collecting" stage of understanding, slowly moving towards the "make predictions" stage. ;) I should also note that even with Vec you can get cache misses on both the pointer to the buffer and the contents to the buffer, since I don't think Vec ever uses an inline buffer. Again, this is something that should be measured. I worry about it because profiles of Gecko's style system long ago showed L2/L3 misses being a significant part of the performance drag, as we walk around selectors, DOM nodes, etc, etc... But at least I had data then. |
We use https://crates.io/crates/smallvec in cases where we want a possibly-inline buffer. |
Each "link" also stores a combinator. With a Vec we’d need to have a dummy/unused combinator in the first or last item of the top-level vector, or have a separate vector of combinators whose length is 1 less than the other top-level vector. |
Ok - a dummy combinator seems like a small price to pay for better cache locality. But we should measure. |
Fixes servo/rust-selectors#107
This change is