Saturday, March 25, 2006 - Posts

The fastest JavaScript string class in the known universe

Ok, so the title of this post is a ridiculous claim as I have not researched the alternatives, but it may actually be true.
 
About 6 or 7 years ago when I was doing Ajax like development before it was the fashionable thing to do (and everyone told me I was nuts) I ran into performance problems on the client due to the large number of string concatenations that were required to render the data to the screen.
 
Unfortunately JavaScript does not offer the same powerful classes like Java's StringBuffer and dotNet's StringBuilder so I had to be a bit creative and come up with a workaround.
 
Let's dive a little bit into why traditional string concatenations are slow with a simplified code example:
 

 
// ** To really demonstrate the differences we simulate iterating through 1000 records
var recordCount = 1000;

function slowString()

{

    // ** Save the time for benchmarking purposes
   

var tick = (new Date()).valueOf();
    var html = "<TABLE>";
    var someData = "dfksdjhfsdkjhdf sdhf skh fskh dfksdkfh";
    var someOtherData = "dfksdjhfsdkjhdf sdhf skh fskh dfksdkfh";

    for(i=0; i<recordCount; i++)
    {
        html += "<TR><TD>" + someData + "</TD>"
        html += "<TD>" + someOtherData +  "</TD>"
        html += "<TD>" + someOtherData +  "</TD>"
        html += "<TD>" + someOtherData +  "</TD>"
        html += "<TD>" + someOtherData +  "</TD></TR>"
    }
    html += "</TABLE>";

    // .. additional code to insert the HTML into the document

    alert("String size: " + html.length + "\nTime taken: "
        ((new Date()).valueOf() - tick) + " milliseconds");

}



On my system it takes 2624 milliseconds to concatenate all the data resulting in a string of about 240KB. So what is really going on here? The problem is that we are performing just over 5000 string concatenations on an ever growing string. Every time we perform a concatenation, even of a single character, a memory block is allocated the size of the original string and the string we are concatenating. Then both strings are copied into this new block. Not only is this not very memory efficient, it is also extremely slow.
 
We can make a simple improvement as shown in the following code example (changes in red bold-italics):
 

function mediumString()
{
    var tick = (new Date()).valueOf();
    var html = "<TABLE>";
    var someData = "dfksdjhfsdkjhdf sdhf skh fskh dfksdkfh";
    var someOtherData = "dfksdjhfsdkjhdf sdhf skh fskh dfksdkfh";

    for(i=0; i<recordCount; i++)
    {
        var temphtml = "<TR><TD>" + someData + "</TD>";
        temphtml    += "<TD>" + someOtherData +  "</TD>"
        temphtml    += "<TD>" + someOtherData +  "</TD>"
        temphtml    += "<TD>" + someOtherData +  "</TD>";
        temphtml    += "<TD>" + someOtherData +  "</TD></TR>"
        html += temphtml;
    }
    html += "</TABLE>";

    // .. additional code to insert the HTML into the document

    alert("String size: " + html.length + "\nTime taken: " +
       ((new Date()).valueOf() - tick) + " milliseconds");
}

 

  
This bit of code is considerable faster as it takes only 730 milliseconds to execute, almost 4 times as fast. We still perform roughly the same amount of concatenations, but the majority of the concatenations are performed on a small 'temphtml' string so there is a lot less data being copied around.
 
Now for the big one, lets refactor the code to use my super fast JavaScript StringBuilder class (changes in red bold-italics).
 

 

function fastString()
{
    var tick = (new Date()).valueOf();

    var html = new CStringBuilder("<TABLE>");
    var someData = "dfksdjhfsdkjhdf sdhf skh fskh dfksdkfh";
    var someOtherData = "dfksdjhfsdkjhdf sdhf skh fskh dfksdkfh";

    for(i=0; i<recordCount; i++)
    {
        var temphtml = "<TR><TD>" + someData + "</TD>";
        temphtml    += "<TD>" + someOtherData +  "</TD>"
        temphtml    += "<TD>" + someOtherData +  "</TD>"
        temphtml    += "<TD>" + someOtherData +  "</TD>";
        temphtml    += "<TD>" + someOtherData +  "</TD></TR>"
        html.append(temphtml);
    }
    html.append("</TABLE>");
    var finalHTML = html.toString();

    // .. additional code to insert the HTML into the document


    alert("String size: " + finalHTML.length + "\nTime taken: " +
        ((new Date()).valueOf() - tick) + " milliseconds");

}


 
The difference is amazing, the code executes in 90 milliseconds, almost 30 times as fast as the original code!
 
So what is the magic ingredient? The StringBuilder class encapsulates a simple JavaScript array. Every time a string is added to this array the size of the array is increased by 1, a relatively fast operation. The string is not even copied but a reference to the string is added to the array, which again is a very fast operation.
When we need the full string (the toString() method) then we let the system do all the work by calling arrayName.join(""), which takes about 10 milliseconds.
 
This code is used on several major e-commerce sites and has made the difference between being sluggish and being very fast. The difference is even more apparent on slower systems.
 
I have placed both the example as well as the full code online.
 
 
The topic of string concatenations as well as other typical improvements and coding guidelines for C#, JavaScript, Java, VB and other languages are available in the Coding Guidelines document I maintain. It can be downloaded for free from my company's website. See my other blog entry for more information.