Answer an id selector from the id map instead of walking - #183
Conversation
There was a problem hiding this comment.
JD this is where we can workout the differences between specs and real world.
The configuration system allows for this granularity of choices and has worked well for the users. In this case the flag ID_DUPES disallow "Duplicated ID" in the same document which seems adequate behaviour. I suggest maintaining the flag but keep it "false" as configured default. For ID lookup this change alone increment speed 2x.
54ff0d9 to
9e019b3
Compare
|
Thank you for the review. Flipping What flipping the default buys, and what it costsOn the 6344-element fixture in
It is that large because The cost is the answer in that case. With What this patch does on its own, with the default left at `true`It changes no answers at either setting. What it adds is that
The last row is the limit of it. |
byId() reaches for document.all and falls back to walking the subtree element by element when it is missing. jsdom does not implement document.all, so every '#id' takes the walk: 2.4ms on a 6300-element document against 43ns for getElementById. jsdom is where most of nwsapi's traffic is, so this is the common case rather than the fallback.
getElementById cannot answer on its own, since a document may carry an id more than once and querySelectorAll matches all of them. It does settle two things in constant time, and each buys back one case:
- whether the id exists anywhere. If the document has none, no descendant of any context has one either, so select('#missing') returns immediately: 2.19ms to 0.0007ms. - where the first one is, in tree order. querySelector wants exactly that, so a lone '#id' against a document is answered by the id map: first('#title') 3.74ms to 0.0007ms.
For select() against a hit the walk still runs, because the duplicates have to be found, but it starts at the first match since none can precede it. Element-scoped queries keep the old path, because the first document-order match may sit outside the context and a match inside it would be missed. So does a detached subtree, which the document's id map knows nothing about.
References:
- Spec: https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid — getElementById returns the first element in tree order
- Spec: https://dom.spec.whatwg.org/#scope-match-a-selectors-string — what a scoped query has to match
- MDN: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
9e019b3 to
efa85cb
Compare
byId()falls back to walking the subtree when document.all is missing, which is every id lookup under jsdom, and that walk measures milliseconds where getElementById measures nanoseconds. getElementById settles two cases in constant time: whether the id exists at all, and where the first one is, which is all querySelector wants.Detail, and how it was checked
byId()reaches for document.all and falls back to walking the subtree element by element when it is missing. jsdom does not implement document.all, so every#idtakes the walk: 2.4ms on a 6300-element document against 43ns for getElementById. jsdom is where most of nwsapi's traffic is, so this is the common case rather than the fallback.getElementById cannot answer on its own, since a document may carry an id more than once and querySelectorAll matches all of them. It does settle two things in constant time, and each buys back one case:
of any context has one either, so select(
#missing) returns immediately: 2.19ms to 0.0007ms.so a lone
#idagainst a document is answered by the id map: first(#title) 3.74ms to 0.0007ms.For
select()against a hit the walk still runs, because the duplicates have to be found, but it starts at the first match since none can precede it. Element-scoped queries keep the old path, because the first document-order match may sit outside the context and a match inside it would be missed. So does a detached subtree, which the document's id map knows nothing about.Extracted from #167 as a standalone change: one file, applies to master on its own, and checked against the benchmark fixture to confirm the results are identical and nothing else moves.
References: the spec, the browser source, and what each part was reasoned from
This patch applies to master on its own. The sixteen in this series were checked by cherry-picking them onto master one after another, in this order and in reverse, and all sixteen land without a conflict.
getElementById.