Use a String Buffer for Better Performance

Update
I survived my vacation in the Sierras! Kings Canyon is an awesome national park and I had a lot of fun hiking at 10,000+ ft (3,048+ meters) (although, my pinky toes did not enjoy my new boots). I noticed that many of you continued to visit, even though I wasnt here, and I thank you for your interest. Starting next week, I will be resuming my previously schedule 2 weekly articles.

While I was gone, Google decided to launch their new browser Chrome, which looks impressive and is based on the Safari Webkit engine (so it is mostly standards compliant). If you havent checked it out yet, you should, because it is another browser that you will need to ensure works with your websites.

Article
Continuing our performance discussions, today we will be discussing a technique to improve JavaScript performance when concatenating strings. The problem occurs when concatenating large strings in JavaScript, or small strings repeatedly. According the JavaScript specifications, a string is an immutable object, so each time a concatenation occurs, a new object (and pointer to that object) must be created and mutated before being returned for use. So if you are concatenating a large string, you are creating multiple copies of a large object, and when repeatedly concatenating a small string, you are creating many needless objects. Both operations will slow down your JavaScript code and provide a very unfriendly user experience.

Here is an example of how not to concatenate a large set of strings:

Example 1: Inefficient Concatenation With +=

var sb = ;

for (var i = 0; i < 100000; i += 1) {
	sb += ABCDEFGHIJKLMNOPQRSTUVWXYZ;
}

alert(sb);

You could also write this using the concat method instead of "+=":

Example 2: Inefficient Concatenation With Concat

var sb = ;

for (var i = 0; i < 100000; i += 1) {
	sb = sb.concat(ABCDEFGHIJKLMNOPQRSTUVWXYZ);
}

alert(sb);

FireBug profiled Example 1 concatenation technique with an average runtime of about 5159.375ms and Example 2 at about 5203.125ms (averaged over 10x each). As you might expect, Example 2 was slightly slower, most likely a result of the constant runtime cost of calling the extra function, concat.

Unfortunately, JavaScript does not have a built in StringBuffer object, but by using Array you can create your own buffer:

Example 3: Efficient Concatenation With Array.Push

var sb = [];

for (var i = 0; i < 100000; i += 1) {
	sb.push(ABCDEFGHIJKLMNOPQRSTUVWXYZ);
}

alert(sb.join());

And you can improve the performance of this technique if you know the Array indices, instead of calling push:

Example 4: Efficient Concatenation With Known Index

var sb = [];

for (var i = 0; i < 100000; i += 1) {
	sb[i] = ABCDEFGHIJKLMNOPQRSTUVWXYZ;
}

alert(sb.join());

FireBug profiled Example 3 concatenation technique with an average runtime of about 315.625ms and Example 4 at about 220.3125ms (averaged over 10x each). As you might expect, Example 4 was slightly faster, because we do not need to call push. Both example 3 and 4 are almost 20x faster than the straight concatenation technique.

Obviously, you could just create an Array anywhere you need to buffer a string, but if you want an optimized StringBuffer object for your JavaScript, here is what I came up with:

Example 5: StringBuffer Object

var StringBuffer = function() {
	this.buffer = [];
	this.index = 0;
};

StringBuffer.prototype = {
	append: function(s) {
		this.buffer[this.index] = s;
		this.index += 1;
		return this;
	},
	
	toString: function() {
		return this.buffer.join();
	}
};

Doing the same operation using this StringBuffer object, will take about 448.4375ms. So it is obviously faster to just work with an Array, however it is not much slower to use the StringBuffer object if you prefer.

There are many examples of StringBuffer objects on the net (with an average runtime of 507.8125ms on the same dataset), but I have improved its performance slightly by including the index, instead of using "Array.push". Keep in mind that you only need to buffer when you are repeatedly concatenating a string or with very large strings. Although, I have not tested across all browsers, my rule of thumb is to use a String Buffer anytime I am concatenating more that 5 times. To test these concatenation techniques yourself, see the Concatenation Test Page.

--------

Additional Notes
Further research led me to realize that Opera actually performs the "+=" concatenation faster than it does the buffer technique, but only marginally (just a few ms on the example page). Safari is also well optimized, only improving about 100ms using the buffer concatenation technique example. Internet Explorer is so bad at the "+=" concatenation that it takes minutes to complete the examples, not seconds. Googles Chrome is the fastest by far, about 3x faster that Safari and Opera in our tests, except when using the concat method, where it performs almost as badly ad IE. So to summarize: buffering is a win in all browsers except Opera (but only marginally worse in Opera), and a huge win for FireFox and Internet Explorer.