Фентуччи ([info]fentucci) wrote,
@ 2006-11-14 15:15:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Current mood: accomplished
Entry tags:javascript, programming

javascript table creation benchmarks

JavaScript Table Creation Benchmarks

The purpose of this exercise is to find out the fastest way to create html tables with javascript.

I am testing 3 methods of creating a table: pure DOM, strings with innerHTML, and a DOM + innerHTML hybrid, where an off-screen table is constructed using strings and then the rows are copied into the body of the target table. Each test is run with 2 options: with and without content, to estimate which method is better for creating empty tables and populating them later as opposed to creating the tables where the content is known at the time of creation. To benchmark real-world performance, the end time is take after a small timeout to allow the table to render. Additionally, each test is run with and without styles to measure the impact of some common css rules.

Test 1. DOM.

Wrappers around the test function:
function domTestWithContent(cnt) {
    domTableTest(cnt, true);
}

function domTestWithoutContent(cnt) {
    domTableTest(cnt, false);
}

Using cloneNode() whereever possible to avoid the overhead of document.createElement. This turns out quite a bit faster than body.insertRow and row.insertCell. The difference between creating the table with and without content is quite big: setting the data requires a loop through all the cells after the table is constructed.

/** 
    cnt: an element where to place the result
    setCellContent: pass true to populate cells with "row, column" 
*/
function domTableTest(cnt, setCellContent) {
    if (typeof(setCellContent) == "undefined")
        setCellContent = false;

    var tab = document.createElement("TABLE");
    var bod = document.createElement("TBODY");
    tab.appendChild(bod);
    for (var r = 0; r < rows; r++) {
        if (r == 0) {
            var row = bod.insertRow(-1);
            for (var c = 0; c < columns; c++) {
                if (c == 0) {
                    var cell = row.insertCell(-1);
                    if (setCellContent)
                        cell.appendChild(document.createTextNode("\u00A0"));
                }
                else {
                    cell = row.firstChild.cloneNode(true);
                    row.appendChild(cell);
                }
                if (setCellContent)
                    cell.firstChild.nodeValue = r + ", " + c;
            }
        }
        else {
            var row = bod.firstChild.cloneNode(true);
            if (setCellContent) {
                var cell = row.firstChild;
                var c = 0;
                while (cell) {
                    cell.firstChild.nodeValue = r + ", " + c;
                    cell = cell.nextSibling;
                    c++;
                }
            }
            bod.appendChild(row);
        }
    }
    cnt.appendChild(tab);
}

Test 2. innerHTML.

Wrappers around the test function:
function stringTestWithContent(cnt) {
    stringTableTest(cnt, true);
}

function stringTestWithoutContent(cnt) {
    stringTableTest(cnt, false);
}

Using arrays to speed up strings performance. Push() may not be the fastest way to add array elements but I believe the overhead is negligible in this context. There is practically no difference between populating the cells with real data or blanks.

/*
    cnt: the element where to place the result
    setCellContent: send true to populate cells with "row, column"
*/
function stringTableTest(cnt, setCellContent) {
    if (typeof(setCellContent) == "undefined")
        setCellContent = false;
    
    var buffer = new Array();
    buffer.push("<TABLE>");
    buffer.push("<TBODY>")
    for (var r = 0; r < rows; r++) {
        buffer.push("<TR>");
        for (var c = 0; c < columns; c++) {
            buffer.push("<TD>");
            if (setCellContent) 
                buffer.push(r + ", " + c);
            else
                buffer.push("&nbsp;");
            buffer.push("</TD>");
        }
        buffer.push("</TR>");
    }
    buffer.push("</TBODY></TABLE>");
    cnt.innerHTML = buffer.join("");
}

Test 3. innerHTML + DOM.

Wrappers around the test function:
function hybridTestWithContent(cnt) {
    stringTableTest3(cnt, true);
}

function hybridTestWithoutContent(cnt) {
    stringTableTest3(cnt, false);
}
</cde>

If innerHTML turns out to be much faster than dom, this method will be good for adding rows to an existing table.

function hybridTableTest(cnt, setCellContent) {
    if (typeof(withstyle) == "undefined")
        withstyle = false;

    var tab = document.createElement("TABLE");
    cnt.appendChild(tab);

    var buffer = new Array();
    buffer.push("<TABLE><tbody>")
    for (var r = 0; r < rows; r++) {
        buffer.push("<tr>");
        for (var c = 0; c < columns; c++) {
            buffer.push("<td>");
            if (setCellContent) 
                buffer.push(r + ", " + c);
            else
                buffer.push("&nbsp;");
            buffer.push("</td>");
        }
        buffer.push("</tr>");
    }
    buffer.push("</tbody></TABLE>");
    
    // create the table in an off-screen div 
    var node = document.createElement("DIV");
    var s = buffer.join("");
    node.innerHTML =  s;
    
    // copy rows
    var oldB = document.createElement("TBODY");
    tab.appendChild(oldB);
    var newB = node.firstChild.firstChild;
    for (var i = 0; i < rows; i++) {
        var r = newB.firstChild;
        oldB.appendChild(r);
    }
}

Running the tests

The settings: which tests to run, how many rows & columns to create, etc
var rows = 300;
var columns = 30;
var loops = 3;
var needStyles = false;
var tests = [   domTestWithContent,     domTestWithoutContent,
                stringTestWithContent,  stringTestWithoutContent, 
                hybridTestWithContent,  hybridTestWithoutContent
            ];
Using recursion and timers instead of a while or for loop to allow each test to complete rendering before proceeding with next iteration. There may be an overhead of a few milliseconds associated with setTimeout(), but it is the same for each run and it is negligible compared to the time taken by the test itself.
function run() {
    var contentElement = document.getElementById("cnt");
    // set (or unset) the class of the target element, to test with or without styles 
    setStyle(contentElement, "withstyle", needStyles);
    var testTimes = new Array(tests.length);
    for (var j = 0; j < testTimes.length; j++)
       testTimes[j] = 0;
    var i = 0;
    var loop = 0;
    
    // this function will be invoked to run each test
    runOneTest = function() {
        // empty the target element
        while (contentElement.firstChild)
            contentElement.removeChild(contentElement.firstChild);
        // start time
        var d1 = (new Date()).getTime();
        // run the test
        tests[i](contentElement);
        
        setTimeout(function() {
            // take the 2nd time measurement after rendering is done
            var d2 = (new Date()).getTime();
            testTimes[i] += (d2 - d1);
            
            i++;
            if (i < tests.length)
                // run the next test in tests array
                runOneTest();
            else if (loop < loops) {
                // run the tests again 
                loop++;
                i = 0;
                runOneTest();
            }
            else {
                // calculate averages and show results
                for (var k = 0; k < testTimes.length; k++)
                    testTimes[k] = Math.round(testTimes[k]/loops);
                showResults(testTimes);
                if (!needStyles) {
                    // chain the 2nd test
                    // not pretty but does the trick
                    needStyles = true;
                    run();
                }
            }
        }, 1);
    };
    
    runOneTest()
}

This is the stylesheet applied when testing with styles. Nothing fancy.
.withstyle {
}

.withstyle TABLE {
  border-collapse: collapse;
} 

.withstyle TD {
   border: solid 1px black;
   white-space: nowrap;
   width: 55px;
   font-family: Sans-serif;
   font-size: 10px;
}
Run the tests
run();

Results

There results are only good for comparisons with one another and not as any kind of absolute performance guideline. So it makes very little sense to mention what kind of hardware/os combo I got them on. But I will anyway.

These numbers are from a dual Xeon 3.6 Ghz workstation with 3GB of ram and no hyper-threading. Windows XP.

style? DOM w/content DOM w/o content innerHTML w innerHTML w/o hybrid w hybrid w/o
opera
w/o 787 427 734 625 781 609
w 1073 557 1047 927 1099 896
firefox 2
w/o 1843 359 1276 802 1198 833
w 1781 463 896 891 1078 911
firefox 1.5
w/o 1906 375 1193 802 1229 901
w 1651 495 995 922 1047 974
internet explorer 6
w/o 2406 1636 1177 766 1609 1198
w 7145 6583 5828 5370 6218 5994

Conclusions

Go Opera. Too bad it's irrelevant. However, on most tests firefox is not far behind, and in the tests with style it is actually ahead. This is, in fact, very suprising - Firefox shows better numbers when styles are set, while common sense tells me that it should be a bit slower, like the rest of the browsers.

What a pathetic display from Internet Explorer. Whoever said it was fast probably never waited for it to finish rendering. Or never tried formatting the pages. Or both. I hope IE7 is better

Internet Explorer screws up all conclusions. It is clear to me that for Firefox and Opera, DOM is the way to go, especially with empty tables. In IE innerHTML is the king. Again, IE7 will hopefully change this.

Run the test here: http://www.benya.com/code/jsbenchmarks/tables.html and post your results. I'd love to see how Safari fares, especially compared to some other browser running on the same platform.




(Post a new comment)


[info]g0ldt00th
2006-11-14 09:33 pm UTC (link)
Here are Safari's results:

Rows 300
Columns 30
Loops 3
Styles false
Browser Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3

domTestWithContent 3488
domTestWithoutContent 426
stringTestWithContent 2885
stringTestWithoutContent 2184
hybridTestWithContent 2845
hybridTestWithoutContent 1990

Rows 300
Columns 30
Loops 3
Styles true
Browser Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3

domTestWithContent 3565
domTestWithoutContent 614
stringTestWithContent 2677
stringTestWithoutContent 2057
hybridTestWithContent 2897
hybridTestWithoutContent 2167

(Reply to this) (Thread)


[info]fentucci
2006-11-15 12:14 am UTC (link)

domTestWithContent 3488
domTestWithoutContent 426


What a difference! Maybe there's a better way of looping through table cells...

(Reply to this) (Parent)(Thread)


[info]g0ldt00th
2006-11-15 12:19 am UTC (link)
Have you seen this before:
http://www.quirksmode.org/dom/innerhtml.html

(Reply to this) (Parent)(Thread)


[info]fentucci
2006-11-15 01:29 am UTC (link)
no, somehow i missed it. I thought I'd read everything on that site.

Anyway, this is a perfect example of a test that does not measure real performance, because it does not wait for the table to render before taking the measurement. And he's not using the full potential of cloneNode...

(Reply to this) (Parent)

Mac FF 1.5
[info]cyclotron
2006-11-14 10:09 pm UTC (link)
Rows 300
Columns 30
Loops 3
Styles false
Browser Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.0.8) Gecko/20061025 Firefox/1.5.0.8

			
1710	domTestWithContent
329	domTestWithoutContent
1178	stringTestWithContent
745	stringTestWithoutContent
1229	hybridTestWithContent
780	hybridTestWithoutContent


Rows 300
Columns 30
Loops 3
Styles true
Browser Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.0.8) Gecko/20061025 Firefox/1.5.0.8

			
1465	domTestWithContent
418	domTestWithoutContent
907	stringTestWithContent
814	stringTestWithoutContent
965	hybridTestWithContent
852	hybridTestWithoutContent

(Reply to this)

IE7 results
(Anonymous)
2006-11-18 01:48 am UTC (link)
Rows 300
Columns 30
Loops 3
Styles false
Browser Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; FDM; .NET CLR 2.0.50727; .NET CLR 1.1.4322)


domTestWithContent domTestWithoutContent stringTestWithContent stringTestWithoutContent hybridTestWithContent hybridTestWithoutContent
3359 2218 1724 1062 2286 1646

Rows 300
Columns 30
Loops 3
Styles true
Browser Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; FDM; .NET CLR 2.0.50727; .NET CLR 1.1.4322)


domTestWithContent domTestWithoutContent stringTestWithContent stringTestWithoutContent hybridTestWithContent hybridTestWithoutContent
6318 5823 5240 2839 4901 3995

(Reply to this)

IE7 vs FF2 vs Opera 9
[info]fentucci
2006-12-03 09:27 pm UTC (link)
Windows XP Pro, Gateway M465E notebook, Core Duo T2500 (2Ghz), 2GB Ram

Rows 300
Columns 30
Loops 3


Internet Explorer 7:
(Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727))

Styles: false

dom w/content dom w/o cntent strings w/content strings w/o content hybrid w/content hybrid w/o content
2620 1573 1417 859 1927 1125

Styles: true

dom w/content dom w/o cntent strings w/content strings w/o content hybrid w/content hybrid w/o content
5375 4448 3964 3245 4042 3537


Firefox 2
(Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0)

Styles: false

dom w/content dom w/o cntent strings w/content strings w/o content hybrid w/content hybrid w/o content
1698 297 1229 729 989 844

Styles : true

dom w/content dom w/o cntent strings w/content strings w/o content hybrid w/content hybrid w/o content
1495 479 849 849 1104 896


Opera9
(Opera/9.01 (Windows NT 5.1; U; en))

Styles: false

dom w/content dom w/o cntent strings w/content strings w/o content hybrid w/content hybrid w/o content
1171 765 610 651 954 1021


Styles: true

dom w/content dom w/o cntent strings w/content strings w/o content hybrid w/content hybrid w/o content
1349 724 1552 1390 1369 1057

(Reply to this) (Thread)

Re: IE7 vs FF2 vs Opera 9
[info]fentucci
2006-12-03 09:34 pm UTC (link)
Interesting. The core duo notebook is faster than the souped-up dual-xeon workstation. And they are about the same age, the workstation is the younger of the two. And they cost roughly the same amount of money.

Opera does not do so well on core duo. I wonder why...

IE7 is still far behind the leaders.

Go firefox!

(Reply to this) (Parent)


Create an Account
Forgot your login?
Login w/ OpenID
English • Español • Deutsch • Русский…