Closed Bug 934995 Opened 11 years ago Closed 11 years ago

Speed up querySelectorAll by doing less allocations

Categories

(Core :: DOM: Core & HTML, defect)

x86_64
Linux
defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla28

People

(Reporter: smaug, Assigned: smaug)

References

Details

(Whiteboard: [qa-])

Attachments

(2 files, 2 obsolete files)

Attached patch patch (obsolete) — Splinter Review
I was wrong about nsAutoTArray. I can see some improvement if we do allocation
just once (in common cases).

gcc doesn't inline the template function without MOZ_ALWAYS_INLINE, yet in this case
inline makes lots of sense.

nsAutoTArray is unfortunately there even for the onlyFirstMatch case, but shouldn't 
be too bad, based on assembly.

https://tbpl.mozilla.org/?tree=Try&rev=1d6555cc2413
Attachment #827393 - Flags: review?(bzbarsky)
Comment on attachment 827393 [details] [diff] [review]
patch

We don't need the nsAutoTArray in the onlyFirstMatch case, do we?  What we should do instead is declare an nsAutoTArray in QuerySelectorAll and pass _that_ as the third arg to FindMatchingElements, so all the appending automatically happens to the nsAutoTArray in that case.  And then the querySelector case won't be affected at all.

As far as then putting the elements into the nsContentList, I'd rather expose something that takes an nsTArray and SwapElements() with it; this would be very fast if we had to heap-allocate our auto array, and much like your manual SetCapacity and append loop otherwise, right?
Attachment #827393 - Flags: review?(bzbarsky) → review-
We can't do SwapElements since we want to collect elements as Element* but contentlist keeps them
alive as nsCOMPtr<Element>.

But I'll make that change to pass nsAutoTArray as param.
Though, that approach is slower in case there are id selectors and also in case someone passes
invalid selecter (jquery for example).

Need to do some template magic
Blocks: 917247
Attached patch v2, more template magic (obsolete) — Splinter Review
Compiler seems to optimize out that dummy for loop in case of
QuerySelector.
Attachment #827393 - Attachment is obsolete: true
Attachment #827537 - Flags: review?(bzbarsky)
> Though, that approach is slower in case there are id selectors 

Hmm.  Why?

> and also in case someone passes invalid selecter

Because in that case we do the auto array ctor/dtor even in cases when we're going to throw an exception, right?  We can address this with using a wrapper around a Maybe<nsAutoTArray> as the class to put stuff into.  That seems like it would be clearer than loops that we depend on optimizing away and AppendElement calls in both branches of an if.  

But I'd like to understand why this has any particular extra cost in the id case, any more than in any other case...
Flags: needinfo?(bugs)
With id case I mean the code in FindMatchingElements which deals with id selectors.
Usually those should return zero or one results.

If we create nsAutoTArray in nsINode::QuerySelectorAll, we create and destroy it always, 
even if FindMatchingElements throws or returns just one result for the id selector.
Flags: needinfo?(bugs)
> Usually those should return zero or one results.

Ah, so we end up doing just the one append and one allocation and all the auto array stuff is pure overhead, ok.

So one option is to use a Maybe<nsAutoTArray>, only init it after the exception/id cases, and use a different API (AppendElementBatched or something?) for appending things when we want them appended to the AutoTArray in the AutoTArray case.  Does that make sense?
How is that better. We'd need to have
if (!onlyFirstMatch)
  maybeThing.construct();

That isn't really much different to
ElementHolder foo; (which is just a container for one null value.)

The if (onlyFirstMatch) { is already there in the loop. Not sure how the AppendElement in it makes it worse. The end of the method is very much no-op in onlyFirstMatch case and seems to be optimized out in onlyFirstMatch case.

Maybe<> isn't totally free either.

But I don't know... I've written this in few different ways and nothing is super pretty, but at least atm
I prefer the approach the patch has.
> We'd need to have
>if (!onlyFirstMatch)
>  maybeThing.construct();

Nah, you'd construct it always but have the thing that the onlyFirstMatch case passes in have a no-op construct() method.

>Maybe<> isn't totally free either.

True...

>and seems to be optimized out in onlyFirstMatch case.

I'm just slightly worried about how much we can rely on that.

Would it help if we were more explicit about doing all that loop stuff if (!onlyFirstMatch)?  And in that case, could we even use a Collector class that has no methods on it, to make sure that code is not even being compiled in the onlyFirstMatch case?  I wonder whether that would compile....
Attached patch v3Splinter Review
The thing doesn't compile if SetCapacity etc aren't defined in the ElementHolder
(even if 'if (!onlyFirstMatch)' is used).

if (len) (where len is always 0) should be pretty strong hint to compiler to
optimize that out, and using length and not onlyFirstMatch let's us optimize out
the case there are no results and onlyFirstMatch is false.
Attachment #827537 - Attachment is obsolete: true
Attachment #827537 - Flags: review?(bzbarsky)
Attachment #828224 - Flags: review?(bzbarsky)
Comment on attachment 828224 [details] [diff] [review]
v3

>+  void SetCapacity(uint32_t aCapacity) {}
>+  Element* ElementAt(uint32_t aIndex) { return nullptr; }

Maybe assert notreached for those?

r=me
Attachment #828224 - Flags: review?(bzbarsky) → review+
https://hg.mozilla.org/mozilla-central/rev/1eeb08e557c2
Status: NEW → RESOLVED
Closed: 11 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla28
Whiteboard: [qa-]
Component: DOM → DOM: Core & HTML
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: