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.
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.