Welcome!

Blog Feed Post

11 keystrokes that made my jQuery selector run 10x faster

As an ASP.NET developer working on the client-side, one problem you’ll encounter is how to reference the HTML elements that ASP.NET web controls generate. All too often, you find yourself wasting time trying to reference TextBox1, when the element is actually rendered as ctl00_panel1_wizard1_TextBox1.

Much has been written about this, including a post of my own, so I won’t go into detail about many of the workarounds. Instead, I want to take a closer look at the performance drawbacks of one popular solution: the [attribute$=value] selector.

By specifying id as the attribute in this selector, you can avoid ASP.NET’s ClientID issues completely. No matter what the framework prefixes your rendered elements with, they still “end with” the ID you specify at design time. This makes the “ends with” selector a convenient alternative to injecting a control’s ClientID property via angle-brackets.

However, are we trading performance for this convenience? If so, how much?

When Craig Shoemaker asked that question while interviewing me for an upcoming episode of Polymorphic Podcast, I realized I didn’t know the answer as clearly as I’d like. So, I decided to do a bit of benchmarking.

In this post, I’ll share the results of that benchmarking, and show you one way to significantly improve the performance of this convenient selector.

The test scenario

One difficulty when analyzing selector performance is that they all perform well on small test pages. Most performance issues aren’t readily apparent until a page grows in complexity and contains many elements. This can easily leave you overly confident in techniques that survive a simple proof of concept, but don’t scale well to practical usage.

Rather than construct a complex demonstration page from scratch to test against, I decided to use an existing page. With over 160 comments at the time of writing (and testing), the Highslide JS .NET project page is an ideal candidate. Its 1,000+ DOM elements are well suited to expose poorly performing selectors.

Enclosing each comment on the page, there’s a div like this one:

<div id="div-comment-35496" class="comment">
  <!-- Comment content here -->
</div>

If you imagine that the “div-“ prefix is “ctl00_”, these IDs are a great substitute for the ClientIDs that ASP.NET controls render within naming containers.

So for purposes of testing, I attempted to select the element div-comment-35496 on that page, assuming that I knew its ID of comment-35496 at design time and that the “div-“ prefix was added at run time. This is identical to the process of selecting the div rendered by an ASP.NET Panel control within a Content Page, for example.

Test methodology

To benchmark each selector, I used the following JavaScript:

var iterations = 100;
var totalTime = 0;
 
// Repeat the test the specified number of iterations.
for (i = 0; i < iterations; i++) {
  // Record the starting time, in UTC milliseconds.
  var start = new Date().getTime();
 
  // Execute the selector. The result does not need
  //  to be used or assigned to determine how long 
  //  the selector itself takes to run.
  $('[id$=comment-35496]');
 
  // Record the ending time, in UTC milliseconds.
  var end = new Date().getTime();
 
  // Determine how many milliseconds elapsed and
  //  increment the test's totalTime counter.
  totalTime += (end - start);
}
 
// Report the average time taken by one iteration.
alert(totalTime / iterations);

This simply executes the selector 100 times, recording how many milliseconds each run takes, and then determines the average.

I had originally recorded and displayed an array of each individual time, but found there was very little variation and stopped tracking each execution. The average is good enough for these purposes.

For each selector, I ran the test five times and then averaged the results, resulting in an average across 500 separate executions in semi-isolated batches. Then, I repeated the process for each major browser.

CERN probably won’t be flying me out to work on the LHC with this level of rigor, but it’s accurate enough for measuring the relative performance change between different selectors.

The baseline

jQuery’s #id selector leverages browsers’ native getElementById routine. Though slower than calling getElementById directly, this is a very fast way to reference our div as a jQuery object:

$('#div-comment-35496')

As you may expect, this browser assisted selector is fastest. In fact, every major browser consistently performed this in less than one millisecond in my testing. Most in less than a third of a millisecond.

To safely use this selector in ASP.NET, we have to manually inject the a control’s ClientID property. Otherwise, any naming container added, removed, or renamed would break our client-side code.

You’ve probably seen that accomplished like this:

$('#<%= comment-35496.ClientID %>')

It looks messy and requires a bit more effort, but it’s fast.

ASP.NET 4.0’s ClientIDMode property promises to eliminate this inconvenience in the long-term, but we’re stuck with it for now. For that matter, with many projects still using ASP.NET 1.x and 2.0, slow to adopt the latest version, we may be stuck with the problem for quite some time to come.

Convenience

To avoid the messy work of manually injecting ASP.NET’s rendered ClientIDs, you may have seen the suggestion that this is an easier way:

$('[id$=comment-35496]')

Indeed, this will successfully select the element that we’re after. Since its ID is div-comment-35496, searching for an element whose ID “ends with” comment-35496 works as desired.

Eliminating the angle-brackets is aesthetically pleasing and it’s less up-front work, but what about performance?

jQuery’s selector engine implements “ends with” by performing this test on every element in question, where value is the attribute you’ve specified and check is the string that you’re searching for:

value.substr(value.length - check.length) === check

That’s not so bad if you’re only doing it once, but doing it for every element on the page is a different story. Remember our test page has thousands of elements.

What’s worse, jQuery has no way to know that only one element should match the pattern. So, it must continue iterating through to the end of the page, even after locating the single element that we’re actually interested in.

How bad is it? Over the course of several hundred executions on the test page, I obtained these average speeds for a single $= selector execution in each browser:

The Chrome numbers are unsurprising, given its speedy V8 JavaScript engine. IE8 and Firefox 3.5 perform admirably too. These newer browsers all executed the selector very nearly as quickly as document.getElementById.

However, IE7 and Firefox 3.0 are substantially slower when executing the “ends with” selector. Remembering the non-trivial difference that Firebug made in this interesting post about JSON parsing speeds, I also tested with and without Firebug enabled in Firefox 3.0 (which turned out to be a significant factor here too).

Even though we’re only talking about milliseconds, the penalty in older browsers is too large to ignore — especially when the vast majority of users are still on IE6, IE7, or Firefox 3.0, not the new generation of faster browsers.

Convenience optimized

Remembering that the #id selector is quick because it leverages the native speed of getElementById, we can help jQuery execute the $= selector more efficiently.

If we modify the selector to descend from an HTML tag before performing the “ends with” search, jQuery can use getElementsByTagName to pre-filter the set of elements to search within. Since getElementsByTagName is a native browser routine, it is much faster than jQuery’s interpreted selector engine.

For example, since we know the element we’re after is a div, we could optimize the previous $= selector like this:

$('div[id$=comment-35457]')

The benefit is substantial:

The optimized selector runs more than twice as quickly in IE7 and Firefox 3.0!

Along the same lines, jQuery’s Sizzle engine is able to select CSS classes faster than it can perform substring searches within arbitrary attributes. So, the selector can be further optimized by descending from both an HTML tag and a CSS class.

Since the particular div we’re testing against has a CSS class of comment, let’s try this selector:

$('div.comment[id$=comment-35457]')

The results?

IE7 doesn’t improve much, but Firefox 3.0 shows another excellent increase in its performance. The reason that Firefox shines here is that it implements a native getElementsByClassName routine that IE7 doesn’t (though IE8 does).

While slower than the getElementById powered #id selector, these optimizations have given us a 3-10x speed increase while referencing exactly the same element as the original $= selector. That’s a pretty good return on the investment of typing a measly eleven characters (div.comment) in front of the selector!

A mountain out of a molehill?

This post may seem like a lot of effort to spend on a seemingly tiny performance differential. In fact, I almost considered leaving this one on the shelf, because it’s often difficult to sell the importance of milliseconds.

But, you know what? Milliseconds count! A performance difference of a few dozen milliseconds is a perceptible delay. A few hundred will alienate your users.

Research has consistently shown a strong correlation between fast sites and higher conversion rates, more user actions per visit, and user satisfaction. Compounded across the thousands or millions of times a particular function in your application will be used, it is absolutely worthwhile to invest a few minutes in order to save a few milliseconds.

Conclusion

I hope you’ve found this information useful. Without hard data, it’s difficult to decide which optimizations are premature and which are worthwhile. Especially since your page is likely to grow more complex over time, I think the data clearly shows that selectors which don’t directly target an ID should always descend from an HTML tag when possible.

Perhaps the most important takeaway is that you must keep in mind how easy it is to write one succinct line of jQuery code that results in a non-trivial loop. The real danger lies in putting a selector like $= within a loop yourself, unaware that what appears to be a simple loop is actually a relatively sluggish nested loop.

The other lesson here is that if speed is crucial, you should inject ClientIDs and use the #id selector (as in the baseline shown above). Even the most optimized “ends with” selector in this post still runs at least one order of magnitude slower than the direct #id selector.

Instead of simply posting that div.class[id$=id] is faster than [id$=id], I wanted to explain the sequence of events that led me to that determination. Armed with knowledge of how I optimized the “ends with” selector, I hope that you have a few new optimization tricks up your sleeve now.

###

Originally posted at Encosia. If you're reading this elsewhere, come on over and see the original.

11 keystrokes that made my jQuery selector run 10x faster

Read the original blog entry...

More Stories By Dave Ward

Dave Ward wrote his first computer program in 1981, using good ‘ol Microsoft Color BASIC and cassette tapes for data storage. Over the years since then, he has had the opportunity to work on projects ranging from simple DOS applications to global telecommunications networks spanning multiple platforms.

Latest Stories
"The Striim platform is a full end-to-end streaming integration and analytics platform that is middleware that covers a lot of different use cases," explained Steve Wilkes, Founder and CTO at Striim, in this SYS-CON.tv interview at 20th Cloud Expo, held June 6-8, 2017, at the Javits Center in New York City, NY.
Everything run by electricity will eventually be connected to the Internet. Get ahead of the Internet of Things revolution and join Akvelon expert and IoT industry leader, Sergey Grebnov, in his session at @ThingsExpo, for an educational dive into the world of managing your home, workplace and all the devices they contain with the power of machine-based AI and intelligent Bot services for a completely streamlined experience.
With tough new regulations coming to Europe on data privacy in May 2018, Calligo will explain why in reality the effect is global and transforms how you consider critical data. EU GDPR fundamentally rewrites the rules for cloud, Big Data and IoT. In his session at 21st Cloud Expo, Adam Ryan, Vice President and General Manager EMEA at Calligo, will examine the regulations and provide insight on how it affects technology, challenges the established rules and will usher in new levels of diligence...
SYS-CON Events announced today that Calligo has been named “Bronze Sponsor” of SYS-CON's 21st International Cloud Expo®, which will take place on Oct 31 – Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA. Calligo is an innovative cloud service provider offering mid-sized companies the highest levels of data privacy. Calligo offers unparalleled application performance guarantees, commercial flexibility and a personalized support service from its globally located cloud platfor...
SYS-CON Events announced today that Calligo, an innovative cloud service provider offering mid-sized companies the highest levels of data privacy and security, has been named "Bronze Sponsor" of SYS-CON's 21st International Cloud Expo ®, which will take place on Oct 31 - Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA. Calligo offers unparalleled application performance guarantees, commercial flexibility and a personalised support service from its globally located cloud plat...
What sort of WebRTC based applications can we expect to see over the next year and beyond? One way to predict development trends is to see what sorts of applications startups are building. In his session at @ThingsExpo, Arin Sime, founder of WebRTC.ventures, discussed the current and likely future trends in WebRTC application development based on real requests for custom applications from real customers, as well as other public sources of information.
FinTechs use the cloud to operate at the speed and scale of digital financial activity, but are often hindered by the complexity of managing security and compliance in the cloud. In his session at 20th Cloud Expo, Sesh Murthy, co-founder and CTO of Cloud Raxak, showed how proactive and automated cloud security enables FinTechs to leverage the cloud to achieve their business goals. Through business-driven cloud security, FinTechs can speed time-to-market, diminish risk and costs, maintain continu...
SYS-CON Events announced today that SkyScale will exhibit at SYS-CON's 21st International Cloud Expo®, which will take place on Oct 31 – Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA. SkyScale is a world-class provider of cloud-based, ultra-fast multi-GPU hardware platforms for lease to customers desiring the fastest performance available as a service anywhere in the world. SkyScale builds, configures, and manages dedicated systems strategically located in maximum-securit...
21st International Cloud Expo, taking place October 31 - November 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA, will feature technical sessions from a rock star conference faculty and the leading industry players in the world. Cloud computing is now being embraced by a majority of enterprises of all sizes. Yesterday's debate about public vs. private has transformed into the reality of hybrid cloud: a recent survey shows that 74% of enterprises have a hybrid cloud strategy. Me...
SYS-CON Events announced today that DXWorldExpo has been named “Global Sponsor” of SYS-CON's 21st International Cloud Expo, which will take place on Oct 31 – Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA. Digital Transformation is the key issue driving the global enterprise IT business. Digital Transformation is most prominent among Global 2000 enterprises and government institutions.
SYS-CON Events announced today that Datera, that offers a radically new data management architecture, has been named "Exhibitor" of SYS-CON's 21st International Cloud Expo ®, which will take place on Oct 31 - Nov 2, 2017, at the Santa Clara Convention Center in Santa Clara, CA. Datera is transforming the traditional datacenter model through modern cloud simplicity. The technology industry is at another major inflection point. The rise of mobile, the Internet of Things, data storage and Big...
"With Digital Experience Monitoring what used to be a simple visit to a web page has exploded into app on phones, data from social media feeds, competitive benchmarking - these are all components that are only available because of some type of digital asset," explained Leo Vasiliou, Director of Web Performance Engineering at Catchpoint Systems, in this SYS-CON.tv interview at DevOps Summit at 20th Cloud Expo, held June 6-8, 2017, at the Javits Center in New York City, NY.
"Outscale was founded in 2010, is based in France, is a strategic partner to Dassault Systémes and has done quite a bit of work with divisions of Dassault," explained Jackie Funk, Digital Marketing exec at Outscale, in this SYS-CON.tv interview at 20th Cloud Expo, held June 6-8, 2017, at the Javits Center in New York City, NY.
"We were founded in 2003 and the way we were founded was about good backup and good disaster recovery for our clients, and for the last 20 years we've been pretty consistent with that," noted Marc Malafronte, Territory Manager at StorageCraft, in this SYS-CON.tv interview at 20th Cloud Expo, held June 6-8, 2017, at the Javits Center in New York City, NY.
"DivvyCloud as a company set out to help customers automate solutions to the most common cloud problems," noted Jeremy Snyder, VP of Business Development at DivvyCloud, in this SYS-CON.tv interview at 20th Cloud Expo, held June 6-8, 2017, at the Javits Center in New York City, NY.