Introduction
Over the last month or so, I've been interested in website performance. There are a lot of useful tools that exist to help you get the most out the HTML/JavaScript/CSS Technologies - One resource in particular I've used is Google Page Speed. Among the recommendation is one to optimise the use of css selectors, however when I looked for evidence to support this rule, every link eventually went back to a Mozilla page written to help people write UI elements in Firefox using the XUL extensions.
What about IE? What about Web Kit? How much difference does CSS really make?
I've divided the task into a few parts.
- Create a test harness that can measure performance speed.
- Work out some common operations and the best way of performing these operations under different CSS conditions (and therefore establish what techniques are best used to determine fastest CSS practice).
- Create some tests that measure the performance hit of some of the inefficient CSS described in the Mozilla article.
In this first blog article I will concentrate on the creating of a test harness.
A Test Harness
The first thing I want is to measure not just the time to run the JavaScript but the time it takes for the browser to render - to achieve this I'm using a setTimeout(...,0) in order to measure the time it would take between making a DOM change and the next time that some JavaScript can run. This isn't totally accurate, but it has seemed good enough to get some indications - I'm not concerned with an exact time for each operation, just an idea of the scale of time that different techniques and CSS make to the render and run time.
So, first I need an HTML page with a percentage bar, a start button and a results table.
Next I need a test function that will run a setup funcion, the actual test, clean up and give me a measurement of the time into my results table.
I return with an additional 500ms to give the browser some time to sort anything out that it needs to. My next problem is that the runTest is asynchronous - if I want to use it to run multiple tests then I could end up with some horrible test definitions. It would be nice if the tests could be defined as an array and the asynchronous nature wrapped up. To do this, I make use of a capture variable and some recursion. The function can also keep track of how many tests have been performed and update the percentage bar.
A first test
So, now we have a (simple) test harness, I shall create an initial speed test. A common operation on large JavaScript applications is to make changes to a table. Specifically we will write a test for adding new rows to an existing table. Perhaps one of the biggest debates for JavaScript DOM performance in this area is the argument between unofficial but widely supported innerHTML property on DOM nodes and using DOM methods such as createElement to create the elements individually.
Those who have tried to use innerHTML with tables in IE have probably discovered that IE does not support innerHTML for elements in the structure of a table. So TD.innerHTML works and DIV.innerHTML = ">table... works but TABLE.innerHTML, TBODY.innerHTML, TR.innerHTML all fail in IE. They seem to strip out the table elements and add the contents of the TD to whatever table element you are altering, regardless of what is allowed, ending up with SPAN elements being the direct children of TABLE elements.
We will bypass this by sticking new table rows inside a dummy<table><tbody></tbody</table> and then extracting out the rows and moving them to our existing table - hence creating rows using innerHTML and measuring the performance with a real life hindrance.
Because the point of this is to measure the performance with relation to CSS, we will perform each test with both 25 and 625 css selectors that target things on the table. We will go into more detail on using CSS selectors in a later part, but at this point I just want to determine if a particular approach is better with lots of CSS versus not very much. I'm also going to go into some detail over the order in which elements are created and added to the DOM - do we do createElement, add it to the DOM and then set the class name or is it better to set the className and then add it to the DOM?
Mix of innerHTML and createElement
First off is an attempt that most people might go for - they know innerHTML is faster but don't want a more complex solution for the sake of speed.
all createElement
Next we have a solution that does not use innerHTML at all.
All innerHTML
And as discussed earlier in the blog our solution that just uses innerHTML to create our elements.
Mix of both - but disconnected from the DOM
Next in our first variation we take the first example but don't attach it to the DOM until after we have set properties like the className and innerHTML.
all document.createElement but disconnected from the DOM
And for comparison, our function that uses createElement, but we wait until everything is ready before attaching to the DOM.
Results
You can view the row constructing test here.
I tested some browsers on my Windows 7 machine and a HTC Desire (Android) and here are the results.
Css Rules | HTC Desire | Chrome | IE9 PP7 | Opera | Firefox 3.6 | IE8 | IE8 In IE7 Mode | |
---|---|---|---|---|---|---|---|---|
Mix of innerHTML and createElement | 25 | 9073 | 778 | 9302 | 31212 | 4760 | 10773 | 10809 |
Mix of innerHTML and createElement | 625 | 13159 | 1845 | 10197 | 34063 | 7381 | 10741 | 10727 |
all createElement | 25 | 3019 | 684 | 9850 | 2458 | 4738 | 10392 | 10725 |
all createElement | 625 | 14160 | 1857 | 11356 | 3704 | 9305 | 11440 | 11461 |
All innerHTML | 25 | 3098 | 728 | 976 | 343 | 1094 | 1198 | 1092 |
All innerHTML | 625 | 11393 | 1777 | 1110 | 709 | 2083 | 1241 | 1167 |
Mix of both - but d/c from the DOM | 25 | 1894 | 687 | 2072 | 378 | 837 | 2161 | 2357 |
Mix of both - but d/c from the DOM | 625 | 11742 | 1808 | 2250 | 757 | 1821 | 2206 | 2417 |
all document.createElement but d/c | 25 | 1671 | 622 | 2252 | 396 | 1314 | 2285 | 2437 |
all document.createElement but d/c | 625 | 12386 | 1823 | 2400 | 830 | 2301 | 2397 | 2507 |
Conclusion
I'll let you draw your own conclusions as to the gap between IE8 on a dual core windows 7 machine and WebKit on a mobile phone. I've highlighted the results that give the best performance, both with lots of css and minimal css. I've created a table (below) which is the time with the optimal time subtracted - E.g. the loss in performance from optimal if you implement a particular technique.
Css Rules | HTC Desire | Chrome | IE9 PP7 | Opera | Firefox 3.6 | IE8 | IE8 In IE7 Mode | Total | |
---|---|---|---|---|---|---|---|---|---|
Mix of innerHTML and createElement | 25 | 7402 | 156 | 8326 | 30869 | 3923 | 9575 | 9717 | 69968 |
Mix of innerHTML and createElement | 625 | 1766 | 68 | 9087 | 33354 | 5560 | 9500 | 9560 | 68895 |
all createElement | 25 | 1348 | 62 | 8874 | 2115 | 3901 | 9194 | 9633 | 35127 |
all createElement | 625 | 2767 | 80 | 10246 | 2995 | 7484 | 10199 | 10294 | 44065 |
All innerHTML | 25 | 1427 | 106 | 0 | 0 | 257 | 0 | 0 | 1790 |
All innerHTML | 625 | 0 | 0 | 0 | 0 | 262 | 0 | 0 | 262 |
Mix of both - but d/c from the DOM | 25 | 223 | 65 | 1096 | 35 | 0 | 963 | 1265 | 3647 |
Mix of both - but d/c from the DOM | 625 | 349 | 31 | 1140 | 48 | 0 | 965 | 1250 | 3783 |
all document.createElement but d/c | 25 | 0 | 0 | 1276 | 53 | 477 | 1087 | 1345 | 4238 |
all document.createElement but d/c | 625 | 993 | 46 | 1290 | 121 | 480 | 1156 | 1340 | 5426 |
This makes it obvious that even though it's not always the best, using innerHTML (even though we had get references afterwards and hack it in by creating a fake holding table) is faster overall.
Next Part - Sorting!