A faster DOM with JIT Compilation?

Introduction

I might be wrong, but It seems that one of the main postulates concerning working with client side Javascript is that the fastest method for manipulating the DOM tree is using the non-standard although ubiquitous innerHTML property, opposed to standard DOM methods createElement, appendChild etc.

However, already back in 2010, Nicolas C. Zakas in his book High Performance Javascript, chapter “DOM Scripting”, has shown that this is not universally true: according to him, Safari 4 and Chrome 3 outperformed the assignment of the innerHTML property.

That made me thinking, what’s the state of the implementation of these approaches today, in 2013-2014.

DOM vs. innerHTML

Link to benchmarks

So I started experimenting with the following markup with three placeholders:

<ul>
    <li class="person">
        <img alt="" src="img/${gender}.png" />
        <dl>
            <dt>First Name</dt>
            <dd>${firstName}</dd>
            <dt>Last Name</dt>
            <dd>${lastName}</dd>
        </dl>
    </li>
</ul>

I used this template for every item to populate a list with 100 randomly generated models with properties firstName, lastName, and gender, and the code for both performance tests looks like this:

innerHTML:

function withHTMLConcatenation(list, entries) {
    var i, len = entries.length, e,
        chunks = ['<li class="person">'];
    for (i = 0; i < len; i++) {
        e = entries[i];
        chunks.push('<img src="img/');
        chunks.push(e.gender);
        chunks.push('.png" />');
        chunks.push('<dl><dt>First Name</dt>');
        chunks.push('<dd>');
        chunks.push(e.firstName);
        chunks.push('</dd><dt>Last Name</dt>')
        chunks.push('<dd>');
        chunks.push(e.lastName);
        chunks.push('</dd>')
        chunks.push('</dl>')
    }
    chunks.push('</li>');
    list.innerHTML = chunks.join('');
}

Using DOM methods:

function withDOMMethods(list, entries) {
    var i, len = entries.length, e,
        fragment = doc.createDocumentFragment();
    for (i = 0; i < len; i++) {
        var e = entries[i],
            item = doc.createElement('li'),
            dl = doc.createElement('dl'),
            img = doc.createElement('img'),
            firstNameTerm = doc.createElement('dt'),
            firstNameDef = doc.createElement('dd'),
            lastNameTerm = doc.createElement('dt'),
            lastNameDef = doc.createElement('dd');
        item.setAttribute('class', 'person');

        img.setAttribute('src', 'img/' + e.gender + '.png');
        item.appendChild(img);

        firstNameTerm.appendChild(doc.createTextNode('First Name'));
        dl.appendChild(firstNameTerm);
        firstNameDef.appendChild(doc.createTextNode(e.firstName));
        dl.appendChild(firstNameDef);

        lastNameTerm.appendChild(doc.createTextNode('Last Name'));
        dl.appendChild(lastNameTerm);
        lastNameDef.appendChild(doc.createTextNode(e.lastName));
        dl.appendChild(lastNameDef);

        item.appendChild(dl);

        fragment.appendChild(item);
    }
    list.appendChild(fragment);
}

Executions of these tests also include JSON string parsing, thus simulating a real life web application, which normally receives a response from a server and parses it. The parsed JSON object is passed as the entries argument in both of the test cases above.

Here are the benchmark.js results displaying count of performed operations in one second (results with a greater number are better):

 

As we can see, the overall best performant is Safari on Mac OS X using the DOM methods,
which in this case performed about 1.5× better than the innerHTML. Notice that the same applies also to iOS7.

On the other hand, desktop Chrome browser has handled the task better using the innerHTML property, although its mobile version on Android produced almost identical results.

Interestingly, that the “old” Android browser (which is being replaced by Chrome in version 4.4) performs better with DOM methods, it even does it better than Chrome with innerHTML on the same device (Android 4.2.2, CyanogenMod, Samsung Galaxy S2).

The others browsers I could test performed clearly better results with the innerHTML.

Comments, Explanations?

Well, I’m not an expert in architectures of web browsers, but I think that the good DOM method results are thanks to the Just-in-time (JIT) compilation of Javascript that almost every modern browser uses nowadays. For example, Google Chrome has V8 and Apple Safari uses Nitro.

Probably JIT compilation makes executing all these DOM methods like createElement or appendChild much cheaper, since it is “native code to native code” instead of “interpreted code to native code”. That’s why not-so-much performant browsers perform innerHTML better: string concatenation is done only in Javascript world followed by just one native call – value assignment to the property innerHTML.

Making Use of the DOM Methods Approach

Most of the web applications use some kind of template mechanism to display results, because hand-crafted DOM manipulation or string concatenation is quite cumbersome.

String concatenation is easy, you can write a template engine that uses JIT compilation with just a few lines of code, like this one.

On the other hand, writing similar template engine that uses DOM methods is much trickier – one cannot get away just by string concatenation, a given piece of HTML template should be parsed and some factory method should be constructed accordingly.

Presenting Jimplate.js

I created Jimplate.js, a little template engine that uses DOM methods to produce elements from a given HTML template and a model. It has a similar usage pattern to other template systems – creating a factory method and executing the method every time something needs to be added:

var templateMarkup = "get it somehow",
    template = Jimplate(templateMarkup),
    list = document.getElementById('a-list');
/* somewhere below */
$.get(url, function(data) {
    var entities = JSON.parse(data);
    list.appendChild(template(entities, { loop: true }));
});

Behind the scenes, Jimplate parses the given template markup using the excellent John Resig’s et al. pure Javascript HTML parser and its SAX style API, generates Javascript code and evaluates it using the relatively new Function API. I think I could use the good old eval as well, to achieve better browser coverage, although new Function is a much safer choice.

The generated Javascript looks like this:

var li0=doc.createElement("li");
li0.setAttribute("class","person");
var img0=doc.createElement("img");
img0.setAttribute("src","img/"+__model.gender+".png");
li0.appendChild(img0);
var dl0=doc.createElement("dl");
var dt0=doc.createElement("dt");
dt0.appendChild(doc.createTextNode("First Name"));
dl0.appendChild(dt0);
var dd0=doc.createElement("dd");
dd0.appendChild(doc.createTextNode(__model.firstName));
dl0.appendChild(dd0);
var dt1=doc.createElement("dt");
dt1.appendChild(doc.createTextNode("Last Name"));
dl0.appendChild(dt1);
var dd1=doc.createElement("dd");
dd1.appendChild(doc.createTextNode(__model.lastName));
dl0.appendChild(dd1);
li0.appendChild(dl0);
__parent.appendChild(li0);

I did several performance optimizations for the generated code, for example:

  • not using regular expressions
  • allowing to pass the Jimplate function a loop: true parameter, thus allowing to construct the whole document fragment in a single factory function call.

Performance comparison results: link

There is about 4 to 7 per cent performance penalty compared to handcrafted DOM method approach on modern browsers – Safari, Chrome. Of course, on less performant browsers, Jimplate approach is even worse than “DOM method” approach was compared to the innerHTML.

Summary

DOM constructing using handcrafted DOM methods or Jimplate is not universally more perfomant than the innerHTML approach, however it shows great results on modern mobile devices, especially those with iOS operating system. It is quite strange that the old Android browser shows better results with DOM compared to the new Chrome for Android, although this should be checked on a real Nexus device before doing any final conclusions.

I guess choosing the right method for populating web application DOM elements depends on a concrete use case, there is no universally “right” choice. Some performance-obsessed people could probably combine these methods, use heuristics or something to determine which is better and serve Jimplate approach for newer devices and the good’ol innerHTML approach for Internet Explorer.

Leave a Reply

Your email address will not be published. Required fields are marked *