Grauw’s blog
Javascript HTML construction benchmark
A while ago I did some experiments on creating an XML document by constructing a string versus creating it by using the DOM methods, and I found that the latter was faster than the former. QuirksMode by Peter Paul Koch has a Javascript benchmark for creating HTML however, which claims that using innerHTML
is 35 times as fast compared to using the DOM (although I can’t reproduce that number in Internet Explorer 8 (IE 7 mode), it is ‘only’ 22 times faster here).
People keep mentioning performance as an argument for using innerHTML
, citing that article, so I thought I’d go and see what the reason was for the difference between his and my findings. It turns out that the benchmark has some flaws that skew the results very much in favour of innerHTML
, which I’ll point out.
What the benchmark does is, it creates a 50×50 table with a ‘*’ in each cell using different approaches. Basically, there are two tests of importance here, which are:
- String construction (using arrays and
array.join('')
) and parsing the result withinnerHTML
- Construction using the DOM methods such as
document.createElement('div')
1. Content is never escaped
First of all, QuirksMode’s innerHTML
tests append strings as follows:
string.push('<td>*</td>');
However, realistically the value of a cell will not be a fixed value, but the value of some variable, which also needs to be escaped to prevent parsing errors. This leads to the following modification:
string.push('<td>'); string.push(escapeHTML('*')); string.push('</td>');
The escapeHTML
function looks like this:
function escapeHTML(str) { return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
This change makes the strings + innerHTML
version roughly twice as slow, but more representative of how it is actually used. The DOM methods already do this implicitly, so there is no change in performance there.
2. Selective HTML usage
Secondly, the QuirksMode test makes use of HTML table elements. This also skews the test results a lot, because when changing them to div
s, the test performs 4 times better in IE! This is what the changed code looks like:
var x = oDocument.createElement('div'); var y = x.appendChild(oDocument.createElement('div')); for (var i=0;i<50;i++) { var z = y.appendChild(oDocument.createElement('div')); for (var j=0;j<50;j++) { var a = z.appendChild(oDocument.createElement('div'));
I wonder if there is some workaround or flag you can set to make it perform more like IE 5.5 in this regard (which is much faster, according to QuirksMode).
Also note that this test only tests element creation — in practise you will also be setting attributes, and when expanding the test with tests for adding attributes using string construction versus the (simple) setAttribute()
method, the relative performance of the DOM version might show further improvements (I didn’t test this though).
3. XML is much faster than HTML
Finally, the HTML DOM is much, much slower than the XML DOM. When using the XML DOM, it turns out to be faster than constructing a string. The following creates and serializes a document in a cross-browser way:
if (document.all) { var oDocument = new ActiveXObject('Microsoft.XMLDOM'); oDocument.async = false; oDocument.preserveWhiteSpace = true; oDocument.validateOnParse = false; oDocument.resolveExternals = false; oDocument.loadXML('<root/>'); } else { var oDocument = new DOMParser().parseFromString('<root/>', 'application/xml'); }
if (document.all) { document.getElementById('writeroot').innerHTML = oDocument.xml; } else { document.getElementById('writeroot').innerHTML = new XMLSerializer().serializeToString(oDocument); }
So, to summarize, when using XML there is really no excuse for constructing a string instead of using the DOM. When using HTML, constructing a string is indeed faster, however innerHTML
is inherently less robust than using DOM operations, as I wrote earlier today, and the difference is not so big as sketched by QuirksMode. Additionally, if your application is really performance-critical, it would be even faster to construct the document in XML, and then serialising/reparsing or transforming it to HTML.
The final test results are:
Method | Running time | Relative performance |
---|---|---|
DOM, using table elements (in original test) | 978 ms | 2274 |
DOM, using div s |
233 ms | 542 |
DOM, using XML | 43 ms | 100 |
Strings, using innerHTML , not escaped (in original test) |
47 ms | 109 |
Strings, using innerHTML , escaped |
63 ms | 147 |
Method | Running time | Relative performance |
---|---|---|
DOM, using table elements (in original test) | 119 ms | 188 |
DOM, using div s |
110 ms | 174 |
DOM, using XML | 63 ms | 100 |
Strings, using innerHTML , not escaped (in original test) |
32 ms | 51 |
Strings, using innerHTML , escaped |
71 ms | 113 |
Note that the results with ‘in original test’ in parenthesis are results from running the original benchmark.
Update: Caching the regular expressions for escaping HTML strings made IE perform a little faster; updated the measurements.
Update: The article (rather, the copy I put on the Backbase BDN) is featured on Ajaxian. Nice!
Grauw
Comments
Re: My studies show by Grauw at 2009-06-12 16:00
Hmm, but in what order did you append the elements to eachother? If you only append any element to its parent once all its children have been appended, it should never get painted until the last moment as the root element gets appended to the main document. So then it should not matter if display is initially set to none or not.
Still, it might be interesting to try my test with table elements using your display:none-trick. Because I suspect what makes it slow is that the browser already does size calculations as they are created.
by Willem Opperman at 2009-06-17 11:44
I had a 1600px div with display:none;
I created 1600 40*40px float:left; divs and appended them to the 1600px wide div(also stored a reference to them in a 3 dimensional array, for various other purposes)
After that I made the 1600px container div display:block;
But that was not a benchmark test, I was developing an application..
Willem Opperman
Re: by Grauw at 2009-06-18 00:56
Right, yeah, in case you append elements directly into the document, to avoid continuous reflows you would have to either detach the parent, give the parent display:none or use a document fragment. Actually, I would recommend giving the latter a try, then you don’t have to mess around with the style.
My studies show by Willem Opperman at 2009-06-11 16:55
In my studies working with js I have found that createElement is in fact very fast.
I dynamically created(content came from MySQL DB) 1600(40*40) 40px*40px div elements.
At first this crashed the browser...
Then I tried using threads which resulted in a 90 second drawing time for the 1600 divs (one at a time), but I could increase the amount of div objects to almost any amount, I went as far as 10,000 (100*100) but this could easily be increased with a relative increase in drawing time.
After that I tried a different approach:
Create all elements with a css rule of display:none; and append
After this traverse through all created elements and apply display:block.
This took the rendering time from 90 secs to 0.4
So the trick is to create everything, and only render it after memory has been allocated to all objects.
Willem Opperman