Skip to main content

Bug Tracker

Side navigation

#908 closed enhancement (fixed)

Opened February 01, 2007 11:08PM UTC

Closed March 24, 2007 10:03PM UTC

merge speed improvments

Reported by: sebastian Owned by:
Priority: major Milestone: 1.1.3
Component: core Version: 1.1.2
Keywords: Cc:
Blocked by: Blocking:
Description

I commented on the blog post regarding the performance comparison of

various frameworks and said I would test a bit.

So I did. Here are the results.

The test page is very simple. It starts like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
function echo(m)
{
   var msg = document.getElementById("msg");
   msg.appendChild(document.createTextNode(m + "
"));
}

$(document).ready(function() {
   var start, end;
   start = new Date();
   var byname = document.getElementsByTagName("div");
   end = new Date();
   echo("Byname count: " + byname.length);
   echo("Byname time: " + (end - start));
   start = new Date();
   var directsel = $("div");
   end = new Date();
   echo("Direct selector count: " + directsel.length);
   echo("Direct selector time: " + (end - start));
   start = new Date();
   var doublesel = $("div div");
   end = new Date();
   echo("Double selector count: " + doublesel.length);
   echo("Double selector time: " + (end - start));
   start = new Date();
   var murdersel = $("div, div div");
   end = new Date();
   echo("Murder selector count: " + murdersel.length);
   echo("Murder selector time: " + (end - start));
});
</script>
</head>
<body>
<pre id="msg"></pre>

This is followed by 285 repetitions of this HTML snippet, which forms
the search base for the expressions above:
<div>
</div>
<div>
   <div>
   </div>
</div>
<div>
   <div>
   </div>
   <div>
       <div>
       </div>
   </div>
</div>

(A total of 1995 divs.)

I ran the tests on a Athlon64 3000+ (clocked at 2.2 GHz), single core, 1

GB RAM, running Gentoo Linux in 64-bit mode. I used three browsers:

Firefox 1.5.0.9

Opera 9.02

Konqueror 3.5.5

I tested each of them several times; the results varied within 5%.

OK, that's all very boring, really. Here's the important thing: I tested

both jQuery 1.1.1 and a version patched with the attached file. The

patched version uses the object identity issue I pointed out in the blog

comment to speed up the duplicate check of merge() and map().

Here are my results:

Firefox:
Without speed patch:

Byname count: 1995
Byname time: 1
Direct selector count: 1995
Direct selector time: 2535
Double selector count: 1140
Double selector time: 4019
Murder selector count: 1995
Murder selector time: 6588

With speed patch:

Byname count: 1995
Byname time: 1
Direct selector count: 1995
Direct selector time: 139
Double selector count: 1140
Double selector time: 2621
Murder selector count: 1995
Murder selector time: 2588


Opera:
Without speed patch:

Byname count: 1995
Byname time: 0
Direct selector count: 1995
Direct selector time: 2579
Double selector count: 1140
Double selector time: 2113
Murder selector count: 1995
Murder selector time: 4945

With speed patch:

Byname count: 1995
Byname time: 0
Direct selector count: 1995
Direct selector time: 45
Double selector count: 1140
Double selector time: 2108
Murder selector count: 1995
Murder selector time: 2105


Konqueror:
Without speed patch:

Byname count: 1995
Byname time: 0
Direct selector count: 1995
Direct selector time: 5043
Double selector count: 1140
Double selector time: 3699
Murder selector count: 1995
Murder selector time: 9540

With speed patch:

Byname count: 1995
Byname time: 0
Direct selector count: 1995
Direct selector time: 128
Double selector count: 1140
Double selector time: 8415
Murder selector count: 1995
Murder selector time: 8490

Only an extensive test suite can verify that everything still works

correctly after applying the patch, but the dramatic performance

improvements especially on the simple selector should make it worth the

while.

The strange slowdown of Konqueror on the second selector also needs

investigating.

Another issue is of course the rather unsophisticated timing method I

used. This was a quick hack, a work of a few hours, and I have no

previous experience with JS profiling.

Thank you for this interesting library.

Sebastian Redl

--- jquery.js   2007-01-22 23:20:57.000000000 +0100
+++ jquery-smartmerge.js        2007-01-23 00:36:54.000000000 +0100
@@ -182,12 +182,12 @@
               return this.prevObject || jQuery([]);
       },
       find: function(t) {
-               return this.pushStack( jQuery.map( this, function(a){
+               return this.pushStack( jQuery.nodemap( this, function(a){
                       return jQuery.find(t,a);
               }) );
       },
       clone: function(deep) {
-               return this.pushStack( jQuery.map( this, function(a){
+               return this.pushStack( jQuery.nodemap( this, function(a){
                       return a.cloneNode( deep != undefined ? deep : true );
               }) );
       },
@@ -216,7 +216,7 @@
       },

       add: function(t) {
-               return this.pushStack( jQuery.merge(
+               return this.pushStack( jQuery.nodemerge(
                       this.get(),
                       t.constructor == String ?
                               jQuery(t).get() :
@@ -498,7 +498,7 @@
                       if ( arg[0] == undefined )
                               r.push( arg );
                       else
-                               r = jQuery.merge( r, arg );
+                               r = jQuery.nodemerge( r, arg );

               });

@@ -592,6 +592,20 @@

               return first;
       },
+       nodemerge: function(first, second) {
+               for(var i = 0, fl = first.length; i < fl; ++i) {
+                       first[i].jQnmarked = true;
+               }
+               for(var i = 0, sl = second.length; i < sl; ++i) {
+                       if(!second[i].jQnmarked) {
+                               second[i].jQnmarked = true;
+                               first.push(second[i]);
+                       }
+               }
+               for(var i = 0, fl = first.length; i < fl; ++i) {
+                       first[i].jQnmarked = false;
+               }
+       },
       grep: function(elems, fn, inv) {
               // If a string is passed in for the function, make a function
               // for it (a handy shortcut)
@@ -638,6 +652,41 @@
               }

               return r;
+       },
+       nodemap: function(elems, fn) {
+               // This version is specialized for arrays consisting entirely of
+               // objects, in particular DOM nodes.
+               // If a string is passed in for the function, make a function
+               // for it (a handy shortcut)
+               if ( typeof fn == "string" )
+                       fn = new Function("a","return " + fn);
+
+               var result = [], r = [];
+
+               // Go through the array, translating each of the items to their
+               // new value (or values).
+               for ( var i = 0, el = elems.length; i < el; i++ ) {
+                       var val = fn(elems[i],i);
+
+                       if ( val !== null && val != undefined ) {
+                               if ( val.constructor != Array ) val = [val];
+                               result = result.concat( val );
+                       }
+               }
+
+               var r = result.length ? [ result[0] ] : [];
+
+               for ( var i = 1, rl = result.length; i < rl; i++ ) {
+                       if(!result[i].jQmmarked) {
+                               result[i].jQmmarked = true;
+                               r.push(result[i]);
+                       }
+               }
+               for ( var i = 1, rl = r.length; i < rl; i++ ) {
+                       r[i].jQmmarked = false;
+               }
+
+               return r;
       }
 });

@@ -834,7 +883,7 @@
                       old = expr;
                       var f = jQuery.filter( expr, elems, not );
                       expr = f.t.replace(/^s*,s*/, "" );
-                       cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+                       cur = not ? elems = f.r : jQuery.nodemerge( cur, f.r );
               }

               return cur;
@@ -905,7 +954,7 @@
                                       // If the token match was found
                                       if ( m ) {
                                               // Map it against the token's handler
-                                               r = ret = jQuery.map( ret, jQuery.isFunction( jQuery.token[i+1] ) ?
+                                               r = ret = jQuery.nodemap( ret, jQuery.isFunction( jQuery.token[i+1] ) ?
                                                       jQuery.token[i+1] :
                                                       function(a){ return eval(jQuery.token[i+1]); });

@@ -926,7 +975,7 @@
                                       if ( ret[0] == context ) ret.shift();

                                       // Merge the result sets
-                                       jQuery.merge( done, ret );
+                                       jQuery.nodemerge( done, ret );

                                       // Reset the context
                                       r = ret = [context];
@@ -976,7 +1025,7 @@
                                                       if ( jQuery.nodeName(this, "object") && tag == "*" )
                                                               tag = "param";

-                                                       jQuery.merge( r,
+                                                       jQuery.nodemerge( r,
                                                               m[1] != "" && ret.length != 1 ?
                                                                       jQuery.getAll( this, [], m[1], m[2], rec ) :
                                                                       this.getElementsByTagName( tag )
@@ -1025,7 +1074,7 @@
               if ( ret && ret[0] == context ) ret.shift();

               // And combine the results
-               jQuery.merge( done, ret );
+               jQuery.nodemerge( done, ret );

               return done;
       },
Attachments (0)
Change History (1)

Changed March 24, 2007 10:03PM UTC by john comment:1

milestone: → 1.1.3
need: → Review
resolution: → fixed
status: newclosed
version: → 1.1.2

I borrowed your 'marked' idea for checking uniqueness and merged some new improvements in (rev [1576]), that are actually very very fast. Thanks for your help!