Software Secret Weapons™

 
Javascript String Concatenation
by Pavel Simakov on 2007-05-16 12:04:20 under AJAX, view comments
Bookmark and Share
 

Introduction
Quick page loading and snappiness of a web site of a key factor to retaining visitors. Visitors do not like to wait! If you make visitors wait, they will go elsewhere. With raise of AJAX web applications it is quite tricky to make pages load fast. Many AJAX like application have to do lots of XML parsing and string manipulation to create the page in the browser. If not done carefully this can create an impression of web site being slow, even if server is lightning fast.

The Problem
As many other software performance problems the answer to the question why an application is slow can have complicated answer. And when the performance problem is in the web browser it is even harder to nail down. First of all, there are too many versions of different browsers out there. Second - browsers have limited debugging capabilities and can't always tell you what is wrong with them. Venkman Mozilla's JavaScript Debugger {1} is great for newer browsers, but Internet Explorer and many others does not have this kind of capability.

For the large part we have to rely on good practices, experimentation and gut feel. Among some general, but pretty useless tips on the Internet, I also found a great report by Dave Johnson {2} that compares performance for XML and JSON approaches to passing data. This report presents lots of important information for dealing with XML data in the web browser. But even after considering Dave's findings you can find you page slow to load. You might have parsed XML blazingly fast, but it might take too long to assemble HTML fragments from this XML. What to do in this case?

If you are building AJAX applications, the old trick of avoiding simple string concatenations is one of the most important tricks to learn. The problem is well known in all languages that support dynamic strings. Consider the JavaScript code sample in the Listing 1. It creates an HTML table using a variable to hold a body of the table.

Listing 1. Inefficient and slow JavaScript code for creating HTML table.

 
 
function slowMakeTable(buf){
	var COLS = 10;
	var ROWS = 500;
	var CELL = "0123456789"

	var start = new Date().getTime();

	buf += "<table>";
	for (var i=0; i < ROWS; i++){
		buf += "<tr>";
		for (var j=0; j < COLS; j++){
			buf += "<td><i>" + CELL + "</i></td>";
		}
		buf += "</tr>";
	}
	buf += "</table>";

	var duration = new Date().getTime() - start;
	document.write("<br>" + (ROWS * COLS) + " cells - " + duration);

	return buf;
} 

Depending on the CPU speed of your computer and the browser running this code sample can take up to 5 seconds! And if you make a text in the cell longer, say 200 characters - this sample can take up to 45 seconds to complete! Main reason - there are over 5000 string concatenations in this example!

To measure precisely how much time string concatenations really takes up I have created a JavaScript function shown on Listing 2.

Listing 2. Test functions for measuring an impact of string concatenations on JavaScript performance.

 

function stringExpand(iter, inc){
	var buf = "";	
	for(var i=0; i < iter; i++){
		buf += inc;
	}
}


function test(){
	var WORD = "0123456789";
	var LOOPS = 5; 

	for (var i = 0; i < LOOPS; i++){
		var iter = i * 1000;
		var start = new Date().getTime();
		stringExpand(iter, WORD);	
		var duration = new Date().getTime() - start;
		document.write("<br>" + iter + " - " + duration);
	}
}

All this test does is concatenate strings. While doing so it varies number of concatenations and the size of the new string that is concatenated. The results are presented in Picture 1.

Picture 1.Performance impact of string concatenation in JavaScript. Tests performed on two browsers Internet Explorer 6.0.2800.1106.xpsp1 (labeled IE) and Mozilla/5.0 Gecko/20050511 Firefox/1.0 (labeled FF) on Pentium 4 CPU at 3 GHz. We ran test() function from Listing 2 while setting WORD variable to contain 1, 10, 100, 1000 characters. Different WORD size test cases are labeled on the picture as "word 1", "word 10", "word 100", and "word 1000" respectively.

As you can clearly see from the Picture 1, the Firefox handles string concatenations for small word size (between 1 and 10 characters) much better than Internet Explorer. Both browsers behave about the same when we start using large word size from 100 to a 1000. They both take huge amounts of time.

The Solutions
There is no surprise here whatsoever. The same problem exists in Java language for example. All JSP developers are advised to avoid String class and += operator and use StringBuffer instead. There is no StringBuffer in JavaScript, but the lessons can be learned.

There are two key steps to minimize string concatenation problem:

  • minimize number of concatenations (so there is no need to reallocate memory)
  • minimize the size of the string that is being appended to (so there is less memory to reallocate)

The improved version of HTML table generator is presented on Listing 3. The changes are very small. Instead of using one buffer buf for all string concatenations, we use additional intermediate buffer row in the inner loop. This revised example of HTML table generator takes less than 0.1 s to run! So we have improved things at least 50 times.

Listing 3. Efficient and fast JavaScript code for creating HTML table.

 
 
function fastMakeTable(buf){
	var COLS = 10;
	var ROWS = 500;
	var CELL = "0123456789"

	var start = new Date().getTime();

	buf += "<table>";
	for (var i=0; i < ROWS; i++){
		var row = "<tr>";
		for (var j=0; j < COLS; j++){
			row += "<td><i>" + CELL + "</i></td>";
		}
		row += "</tr>";
		buf += row
	}
	buf += "</table>";

	var duration = new Date().getTime() - start;
	document.write("<br>" + (ROWS * COLS) + " cells - " + duration);

	return buf;
}

Huge performance boost can be obtained by simply using intermediate strings! It is possible to create StringBuffer-like class in JavaScript to gain even more performance boost. When I posted a link to this article on comp.lang.javascript one of the readers (Lasse Reichstein Nielsen) provided almost complete implementation for a JavaScript StringBuffer. Here it is:


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

 StringBuffer.prototype.append = function append(string) { 
   this.buffer.push(string); 
   return this; 
 }; 

 StringBuffer.prototype.toString = function toString() { 
   return this.buffer.join(""); 
 }; 

 var buf = new StringBuffer();

 buf.append("hello");
 buf.append("world");

 alert(buf.toString());

Reader's Feedback

  • Tue May 15 21:36:12 EDT 2007
    Hi, I test your codes both in IE 7.0 and Firefox / 2.0.0.3. With IE what you said is correct, the fastMakeTable is much faster. But with Firefox, the 2 functions run at almost the same speed. slowMakeTable() is even a little faster.

References

  1. Venkman JavaScript Debugger
  2. JavaScript Benchmarking IV: JSON Revisited

Detailed reviews of wireless internet providers which also provide cheap hosting along with the services like backup to protect your data.

Comments (29)

  • Comment by Álvaro G. Vicario — October 19, 2007 @ 7:31 am

    Take into account that the suggested StringBuffer implementation is merely a proof of concept. It works fine in Explorer but in Firefox it’s awfully slow when actually converting to string (toString method).

  • Comment by Mark — October 29, 2007 @ 1:18 am

    I was thinking to doing same benchmark. I just discovered this yesterday doing AJAX data fetches for a google map. I though it was the Google map running slow on IE, but turned out it was the string concats during JSON parsing. 2 MINUTES on IE7 vs 2 SECONDS on FF to parse a string element a few thousand chars long. Have they never heard of pascal style strings, with a length a field so you don’t have to walk the whole damn string to find the end if you want to append a char?

    Kudos to Google for getting maps to work so well on IE. If I could wish the IE codebase to just vanish of the face of the earth, I’d do it in a second. Don’t they realize the extreme ill will they have created in the developer community? It will be the eventual death of them. </rant>

  • Comment by Andrew — April 15, 2008 @ 12:12 am

    I know this is an old post…but still quite relevant. I happened to look at the source of this site: bell.ca
    There appear to be hundreds of lines of JS (analytics code?) that are concatenated like this:
    +”d.s_c_in++;s.m=function(m){return (”+m).indexOf(‘{‘)<0};s.fl=funct”
    +”ion(x,l){return x?(”+x).substring(0,l):x};s.co=function(o){if(!o)r”
    +”eturn o;var n=new Object,x;for(x in o)if(x.indexOf(’select’)<0&&x.i”
    I assume it gets eval’d somewhere and is simply an obfuscation technique…but wouldn’t this code perform horribly based on the data you show here?

  • Comment by Sirjon — July 9, 2008 @ 4:13 am

    Hi ,The string buffer code prodided is working prefectly ..
    thanks alot buddy, it helped me get out of big trouble,But I want to know if there is any standard js function for this purpose or not
    thnaks

  • Comment by swetha — July 18, 2008 @ 7:31 am

    hi nice article…..

  • Comment by Errol — August 9, 2008 @ 11:30 pm

    Will give it a try for JSON table formatting..thought…I have a reverse function for creating a table to JSON but still slow any ideas?

  • Comment by TomP — August 13, 2008 @ 6:01 pm

    Very helpful. I’ve been playing with the Rico objects (openrico.org) and was fairly impressed – but when I tried to use their “export” functionality… my smiles turned into frowns. It took over 45 minutes to export approx. 5000 rows. (That explained why they set an artificial limit to 1000 rows – which I changed).

    I found the places where the export was building its results – and sure enough – - tons of string concatenations.

    I modified the code to take advantage of this technique… and those same 5000 rows exported to the Microsoft Excel object within a few seconds.

    Nice.

    Now I just have to figure out how to speed up the Microsoft Excel object (that still takes a couple minutes)

  • Comment by Dave — September 17, 2008 @ 11:14 pm

    Excellent technique! I did a quick test with a string of 100 characters and 10,000 concatenations. Your StringBuffer improved Firefox 3’s execution time by 100% and IE 6’s by 700-750%. Nice work!

  • Comment by David — December 6, 2008 @ 9:43 am

    BROWSER DIFFERENCES!

    The savings are real, but in my tests,not necessarily as much as shown by others.

    However, the big story to me is the difference between browsers and the browsers account for most of the differences: For example, I ran a test on a 4 year old laptop with 100 char insert, 4000 iterations:

    IE 6.0.290: +=> 6125 sb> 6016
    FF 3.04: +=> 63 sb> 55
    Safari 3.2.1: +=> 19 sb>27 (yes, += was consistently faster)
    Google Chrome: +=> 1 sb> 0

    The huge story is the speed of the Chrome browser: even with 60,000 instances (from 1 to 1000 chars, 1000 to 5000 iterations, with both += and stringBuffer, the screen seemed to “snap” into view: all times were 0-2 which is beyond the real precision of the Date() function.

    I was told that there are new javascript engines in development from IE, Apple, and others. It will be interesting to see relative speeds with the next generation. If they can come close to the Google engine, we can all be comfortable with += for concatenation.

  • Comment by David — December 6, 2008 @ 9:46 am

    There is no reason to return a value from the append() function of StringBuffer. Removing the “return this;” line can save 20% or more in the StringBuffer performance.

  • Comment by Gary Little — December 9, 2008 @ 7:13 pm

    I tried the StringBuffer technique on MSIE and my new routine takes 1/3 of the time of the old routine. Pretty good, though still not as fast as Safari or Chrome.

  • Comment by iTNyeK — January 3, 2009 @ 6:25 am

    I tried your code in Firefox 3, and suprised the slowMakeTable is faster 2 twice more. But in IE it slower 2.5 twice.

  • Comment by BeatabetMeme — January 9, 2009 @ 12:44 pm

    Stringbuffer made one of my functions 87.5 % faster !
    This, plus a couple of other tweaks and I can now handle 45,000 + records, where before my code was freezing the interface for an agonising second or two with a mere 420 records.
    Thanks Lasse, Thanks Pavel !

  • Comment by Robert K. — February 24, 2009 @ 7:33 pm

    Note that string concatenation performance varies dramatically across browsers. The StringBuffer approach is, in fact, slower on some platforms. (Sorry, don’t have data to back this up at the moment, but I’m pretty sure this is the case.)

    Note, too, that the StringBuffer class is 100% redundant with the native Array API. E.g. the sample code:
    <blockquote>
    var buf = new StringBuffer();
    buf.append(“hello”);
    buf.append(“world”);
    alert(buf.toString());
    </blockquote>
    … can be rewritten like so:
    <blockquote>
    var buf = [];
    buf.push(“hello”);
    buf.push(“world”);
    alert(buf.join(”));
    </blockquote>

    … and avoids all the overhead of creating extra object instances, and making the intermediate method invocations to StringBuffer.

  • Comment by Bobby — March 17, 2009 @ 11:56 pm

    I can not say thank you enough. Firefox was handling about 1000 string concats in sub second time and I discovered after I made a major update to my site that IE was taking 10-15 seconds to do the same thing. I implemented your StringBuffer() code and that time went down to maybe a second. And I also found a classic coding error where I was using ‘=’ for comparison instead of ‘==’. I was calling my long running function 3 times instead of once. Firefox was so quick I never saw a problem. Now both browsers are running the code almost instantly.

    Thank you again for sharing.

    Bobby

  • Comment by Jhuni — March 25, 2009 @ 11:53 pm

    Hi that doesn’t look like it is going to be a speed improvement. Actually I think that looks slower. Push makes it much slower:

    Array.prototype.push = function() {
    var mylen = this.length;
    var arglen = arguments.length;

    for( var i = 0; i < arglen; i++ ) {
    this[mylen+i] = arguments[i];
    }
    return this.length;
    };

    You need to parse all the arguments, their length and so on and you also return values that you wouldn’t be using. Multiply that by 5000. If you eliminate that it might have some speed improvement. Here is join:

    Array.prototype.join = function(seperator){
    var rval = ”;
    var mylen = this.length;

    for( var i = 0; i < mylen; i++ ) {
    rval += this[i];

    if( i != mylen-1 ) {
    rval += seperator;
    }
    }
    return rval;
    };

  • Comment by amit — March 27, 2009 @ 10:51 am

    Thank you!! my code runs smoothly now on IE6, the regular concatenation took something like 10-15 secs, 100% cpu…

  • Comment by Robert — May 8, 2009 @ 3:38 am

    Thanks a lot! The StringBuffer tip helped me save my application. Already ran smooth of Firefox and Opera, but IE was unacceptable slow untill applying the StringBuffer idea.

    StringBuffer is ofcourse just an array that collects substrings.
    So there was slight performance improvement to be gained by just using a simple array and assigning substrings directly:
    var buf = [];
    buf[buf.length] = ’substr1′;
    buf[buf.length] = ’substr2′;
    buf[buf.length] = ’substr3′;
    var strcomplete = buf.join(”);

  • Comment by David Jacob Jarquin — August 1, 2009 @ 12:31 am

    I used Shared Object of flash to persist a cookie at any navigator, i save the data as xml string and save it locally and recover data as xml string and convert it to xml Oject, with the following:

    var _xml = {
    _str2xml : function(strXML){
    if (window.ActiveXObject)
    {
    var doc=new ActiveXObject(“Microsoft.XMLDOM”);
    doc.async=”false”;
    doc.loadXML(strXML);
    }
    // code for Mozilla, Firefox, Opera, etc.
    else
    {
    var parser=new DOMParser();
    var doc=parser.parseFromString(strXML,”text/xml”);
    }// documentElement always represents the root node
    return doc;
    },
    _xml2string : function(xmlDom){
    var strs = null;
    var doc = xmlDom.documentElement;
    if(doc.xml == undefined){
    strs = (new XMLSerializer()).serializeToString(xmlDom);
    }else strs = doc.xml;
    return strs;

    }
    }

    var xmlDoc = _xml._str2xml(“<root></root>”);
    alert(xmlDoc.childNodes.length);

    alert(“Xml string is:” + _xml._xml2string(xmlDoc));

  • Comment by Florian — August 21, 2009 @ 11:32 am

    Great post, great comments, thank you!

    Using the array.push() and .join() methods instead of string1 + string2 + .. let my performance nightmares vanish.
    IE6 down from 2000 msec to 60 msec

  • Comment by Deepak kumar — January 20, 2010 @ 3:18 am

    Great post, great comments, thank you!

  • Comment by tetsuo — February 21, 2010 @ 10:42 am

    > There is no reason to return a value from the append() function of StringBuffer. Removing the “return this;” line can save 20% or more in the StringBuffer performance.

    There is a reason.
    "return this" provides 'fluent interface'.
    The sample code can be written like below:

    alert(new StringBuffer().append("hello").append("world").toString());

  • Comment by wholesale shoes — April 6, 2010 @ 10:25 pm

    Wow, thanks for the insightful post. I look forward to reading more from you.
    http://www.cheapshoeshop.com/Nike-Air-Jordan-Shoes/Nike-Air-Jordan-7.html

  • Comment by Tricky Life — October 1, 2010 @ 9:12 am

    Thank you for the article. It solved my problem with the concatenation.. I was not sure about the operator :p

    <a href='http://www.trickylife.com/internet-tips/bom-sabado-orkut-is-hacked-again/' rel='nofollow'>Bom Sabado</a>

  • Comment by Tricky Life — October 1, 2010 @ 9:13 am

    Thank you for the article. It solved my problem with the concatenation.. I was not sure about the operator :p

    http://www.trickylife.com/internet-tips/bom-sabado-orkut-is-hacked-again/

  • Comment by Neeraj — February 3, 2011 @ 4:07 pm

    Thanks, Pavel! It was a great article and gave us a clear insight of the efficient string handling in javascript.

  • Comment by sakthi — May 6, 2011 @ 3:30 am

    thanks but i need how u post ur comments on the same form can u tell me the code

  • Comment by Rob Mitchell — May 12, 2011 @ 2:18 pm

    How does this compare to using the String.concat(…) function? After checking out the Rhino source for Firefox, it appears that the join method for arrays will cause more code to be executed than the concat method for strings. It seems to me that a method made specifically for a purpose would be more performant than an operator override that can have multiple purposes.
    Other than that….Great stuff.
    TANX.Rob

  • Comment by Mike Mitchel — September 30, 2011 @ 11:19 pm

    This is no longer effective. All of the operators, +, += and concat perform up to 10 times better than the join function.


Leave a comment


  Copyright © 2004-2014 by Pavel Simakov
any conclusions, recommendations, ideas, thoughts or the source code presented on this site are my own and do not reflect a official opinion of my current or past employers, partners or clients