HowTo: jqspm - jQuery Selector Performance Monitor

posted by sacah on javascript, howto, jquery, programming,

Quick Start:

Head over to the jqspm repo, grab the files and checkout the README.

So I'll assume you've grabbed the files and have read the brief README. So here I'll cover how jqspm (jQuery Selector Performance Monitor) monitors selectors, and how to interpret the results.

How it works

When you call jqspm.start() the script wraps the existing jQuery() and jQuery.find() functions in monitoring functions. These monitoring functions capture what selectors are being passed in, they then call the original jQuery functions and record how long it took for them to execute, and how many elements they returned.

Once you have monitored some calls you'll want to printReport, this is where there may be some confusion, so I'll take some time explaining all the points I think might be confusing. Below is a picture of a report printed via console.table.




Selectors column
Here are the list of selectors that were called during the monitoring. You will notice they are separated by an =>, so the following:
$('UL').find('LI');
Will result in a selector looking like:
"UL => LI"
As you can see from the picture above, this will keep going on the longer you chain calls to jQuery() or jQuery.find().


totalCount column
This is the total number of elements that were returned by the whole chain of selectors.


Counts column
This is a list of the number of elements each part of the selector chain returned. So a selector like:
$('UL').find('LI');
Will return a count of:
[ 3, 11 ]
This tells you it found 3 ULs, and 11 LIs.

totalTime column
This is the combine time it took to find all elements that were returned by the chain of selectors. This is a good number to look at to pick up inefficient selector chains.

Timings column
This is a list of timings for each selector in the chain, so using the same chain as above:
$('UL').find('LI');
Will return:
[ 3, 5 ]

Oddities

Single Id selectors:
Queries like
$('#menuHolder').find('UL');
 Will only return counts/timings for the UL selector. I am looking at adding in the ability to monitor single ID selectors, but it's not high on the priority list as that query should only ever return 1 element, and should be instant.

The problem this currently posses is counts and timings for the above query will only contain 1 number, even though there are 2 selectors occurring, just keep that in mind when investigating results.

Chaining saved selectors:
Queries like
var $obj=$('BODY');
$('UL');
$obj.find('UL');
Will give you 3 results
"BODY"
"UL"
"BODY => UL"
The one I want to bring your attention to is the last one, it's counts and timings will look like this
[ "", 3 ]
This is because you chained a saved selector, $obj, so you know that BODY was from a previous selector.

What to look for

Here are a few screenshots showing how this can help, this is a real selector example I've found in production code. Though in the production environment there were a ton of other elements within each LI, this was causing the iPhone 5 to take 8.5 seconds to execute. By changing it all into the one selector that time was cut to about 50ms.

When you see a large number of elements returned early in the query chain, you should look at ways to optimise. In the IE8 example below, jQuery found 1557 LIs, and each of these was added to an array of elements.

It then loops through that array, executing the second selector (UL) on each element, so it executes it 1557 times. Each time it does this it needs to copy the elements it found back to the main collection array, and in browsers with poor memory management, or systems with limited memory, this can be costly.

Notice how when we moved the second selector into the first query, eliminating the need to loop, query, add to array, we get much better performance.

IE8





Nexus 4 - Chrome



Wrap it up

Hope that has made sense, and you find this useful, let me know any questions you have, or feedback, I'll update this page once I add more features.