- From: Boris Zbarsky <bzbarsky@MIT.EDU>
- Date: Wed, 19 Oct 2011 11:17:48 -0400
- To: Yehuda Katz <wycats@gmail.com>
- CC: Alex Russell <slightlyoff@google.com>, public-webapps@w3.org
On 10/19/11 5:41 AM, Yehuda Katz wrote: > Right. I'm representing the position of jQuery. Sizzle (John's selector > engine, used by jQuery) chose to optimize certain common selectors after > an analysis of selectors used by jQuery found that a large percentage of > all selectors used were a few simple forms that were amenable to > getElement(s)By* optimizations. I provided a link to the code that > implements this earlier in this thread. OK, so the code is at https://github.com/jquery/sizzle/blob/master/sizzle.js#L1150-1233 and does the following optimizations: 1) If the selector is a vanilla tag, use getElementsByTagName instead. This works, at least in Gecko, from userland due to the fact that getElementsByTagName results are cached until GCed. If I test with a different argument for every call, the performance of querySelectorAll and getElementsByTagName is basically identical.[1] Doing a similar optimization in the C++ code would be somewhat difficult, since you don't know when to drop your cache, but would be doable. I would expect the common case here to be repeated queries for the same tag name.... In any case, in WebKit the caching effect is not present and the getElementsByTagName codepath is maybe 10% faster than querySelector. 2) As above, for class names. Works for the same reasons. WebKit seems to have a getElementsByClassName cache too. 3) Mapping Sizzle("body") to document.body. This isn't a valid optimization for querySelector, since there can in fact be multiple <body> tags and since furthermore document.body can be a <frameset>. A UA could try to optimize this case by keeping track of the <body> tags and such, at some cost on every DOM mutation. 4) Mapping Sizzle("#id") with document a context to getElementById("id"). This isn't a valid optimization for querySelector because there can be multiple elements with the same id; in fact that's pretty common. A UA can work around this in various ways (e.g. WebKit only makes the case when the id is unique take a fast path last I checked), though. That sound about right? -Boris [1] I tested by running the script below against http://www.whatwg.org/specs/web-apps/current-work/ and got these results in Chrome: querySelectorAll('div'): 915 Array.prototype.slice.call(getElementsByTagName('div'), 0): 948 querySelectorAll('div'+i): 938 Array.prototype.slice.call(getElementsByTagName('div' + i), 0): 847 querySelectorAll('.div'): 889 Array.prototype.slice.call(getElementsByClassName('div'), 0): 8 querySelectorAll('.div'+i): 910 Array.prototype.slice.call(getElementsByClassName('div' + i), 0): 824 and these in Firefox: querySelectorAll('div'): 767 Array.prototype.slice.call(getElementsByTagName('div'), 0): 20 querySelectorAll('div'+i): 773 Array.prototype.slice.call(getElementsByTagName('div' + i), 0): 749 querySelectorAll('.div'): 817 Array.prototype.slice.call(getElementsByClassName('div'), 0): 8 querySelectorAll('.div'+i): 812 Array.prototype.slice.call(getElementsByClassName('div' + i), 0): 809 Script is: // Prevent dead-code optimizations var holder; function time(f) { var start = new Date; for (var i = 0; i < 100; ++i) holder = f(i); return (new Date) - start; } tests = [ { description: "querySelectorAll('div')", func: function() { return document.querySelectorAll("div") } }, { description: "Array.prototype.slice.call(getElementsByTagName('div'), 0)", func: function() { return Array.prototype.slice.call(document.getElementsByTagName("div"), 0); } }, { description: "querySelectorAll('div'+i)", func: function(i) { return document.querySelectorAll("div" + i) } }, { description: "Array.prototype.slice.call(getElementsByTagName('div' + i), 0)", func: function(i) { return Array.prototype.slice.call(document.getElementsByTagName("div"+i), 0); } }, { description: "querySelectorAll('.div')", func: function() { return document.querySelectorAll(".div") } }, { description: "Array.prototype.slice.call(getElementsByClassName('div'), 0)", func: function() { return Array.prototype.slice.call(document.getElementsByClassName("div"), 0); } }, { description: "querySelectorAll('.div'+i)", func: function(i) { return document.querySelectorAll(".div" + i) } }, { description: "Array.prototype.slice.call(getElementsByClassName('div' + i), 0)", func: function(i) { return Array.prototype.slice.call(document.getElementsByClassName("div"+i), 0); } } ]; function runTest() { var results = [] for (var i = 0; i < tests.length; ++i) results.push(tests[i].description + ": " + time(tests[i].func)); document.write(results.join("<br>")); }
Received on Wednesday, 19 October 2011 15:18:17 UTC