Introduction
I'm currently working with a complex AJAX control (Grid), and I have to concatenate a lot of strings (HTML) to render them on the client side with JavaScript.
In JavaScript, each string is immutable (like .NET), and this means that every time the engine concatenates a string using + or +=, it creates a new instance of string and disposes the old one. This, in some browsers (mainly Internet Explorer), makes the concatenation very slow. In other browsers like Mozilla Firefox or Safari, these concatenations are optimized before getting executed (JavaScript engine in Firefox 3 is absolutely amazing!!!).
Unfortunately, I have to support Internet Explorer 6 and Internet Explorer 7, and as everybody knows, this is a very big problem.
Background
Normally, to optimize string concatenation, Array and Array.join methods are used. In ASP.NET Ajax Framework 1.0, this approach is used in StringBuilder and performance is very good in Internet Explorer. The following code explains this approach:
StringBuilderEx = function()
{
this._buffer = new Array();
}
StringBuilderEx.prototype =
{
append : function(text)
{
this._buffer[this._buffer.length] = text;
},
toString : function()
{
return this._buffer.join("");
}
};
We can optimize this class using a trick in JavaScript: every function call has a cost and we can use a function pointer to redirect append to push and toString to join:
var StringBuilderEx = Array;
Array.prototype.append=Array.prototype.push;
Array.prototype.toString=Array.prototype.join;
It seems like very strange code, but it is very quick because we jump 1 function call for every append and 1 for every toString.
Using append alone to generate HTML makes the code very difficult to maintain. ASP.NET AJAX framework has an additional method that formats text similar to the C# format, which extends String (String.format). The following example shows the same string concatenation using append alone and with append and String.format together:
var sb = Sys.StringBuilder();
sb.append("<div style='width:");
sb.append(width);
sb.append("px;height:");
sb.append(height);
sb.append("px'></div>");
var sb = Sys.StringBuilder();
sb.append(String.format("<div style='width:{0}px;height:{1}px'></div>", width, height));
Unfortunately, the performance with String.format is very bad in Internet Explorer. I searched on the Internet for a way to do append and format with good performance, and I found some good examples. I combined them with some of my original ideas.
I implemented two solutions to have append and format together (appendFormat):
- Using regular expression (syntax "Hello {0}! {1}"): Using this /\{(\d+)\}/g I search for all decimal numbers within { and }, and I use a function to replace the argument.
- Using split and join (syntax "Hello ?! ?"): Using ? (Question mark) symbol to replace arguments, I just split once and join twice in the
toString function.
var StringBuilderEx = Array;
Array.prototype.append=Array.prototype.push;
Array.prototype._convertToArray=function(arguments)
{
if (!arguments)
return new Array();
if (arguments.toArray)
return arguments.toArray();
var len = arguments.length
var results = new Array(len);
while (len--)
{
results[len] = arguments[len];
}
return results;
};
Array.prototype.appendFormat=function(pattern)
{
var args = this._convertToArray(arguments).slice(1);
this[this.length]=pattern.replace(/\{(\d+)\}/g,
function(pattern, index)
{
return args[index].toString();
});
};
Array.prototype.appendFormatEx=function(pattern)
{
if (this._parameters==null)
this._parameters = new Array();
var args = this._convertToArray(arguments).slice(1);
for (var t=0,len=args.length;t<len;t++)
{
this._parameters[this._parameters.length]=args[t];
}
this[this.length]=pattern;
};
Array.prototype.toString=function()
{
var hasParameters = this._parameters!=null;
hasParameters = hasParameters && this._parameters.length>0;
if (hasParameters)
{
var values = this.join("").split('?');
var tempBuffer = new Array();
for (var t=0,len=values.length;t<len;t++)
{
tempBuffer[tempBuffer.length]=values[t];
tempBuffer[tempBuffer.length]=this._parameters[t];
}
return tempBuffer.join("");
}
else
{
return this.join("");
}
};
Using the Code
I wrote a custom class in JavaScript called StringBuilderEx that contains the following methods:
append: Append string
appendFormat: Append and format string using the same C# syntax ("Hello {0}")
appendFormatEx: Append and format string using a custom syntax ("Hello ?")
toString: Retrieve the concatenation of strings
var sb = new StringBuilderEx();
sb.append("Hello");
sb.appendFormat("Hello {0}!!! {1}", "World", "Bye");
sb.append("Hello ?!!! ?", "World", "Bye");
Performance
I tested it on Vista with Internet Explorer 7:
Run Tests Append Strings (20000)...
- Test concatenate
strings with + : 17552 ms.
- Test concatenate
strings with Sys.StringBuilder (append): 667 ms.
- Test concatenate
strings with StringBuilderEx (append): 461 ms.
Run Tests Append Strings with Format (20000)...
- Test concatenate
strings with Sys.StringBuilder (appendFormat): 4204 ms.
- Test concatenate
strings with StringBuilderEx (appendFormat): 1658 ms.
- Test concatenate
strings with StringBuilderEx (appendFormat Split+Join): 1225 ms.
I also tested it on Vista with Firefox 3.0b5:
Run Tests Append Strings (20000)...
- Test concatenate
strings with + : 109 ms.
- Test concatenate
strings with Sys.StringBuilder (append): 211 ms.
- Test concatenate
strings with StringBuilderEx (append): 152 ms.
Run Tests Append Strings with Format (20000)...
- Test concatenate
strings with Sys.StringBuilder (appendFormat) : 1962 ms.
- Test concatenate
strings with StringBuilderEx (appendFormat): 770 ms.
- Test concatenate
strings with StringBuilderEx (appendFormat Split+Join): 497 ms.
Points of Interest
I don't know if this is the fastest StringBuilder (probably no), but actually it is quite fast with Internet Explorer 7 and FF3 (even more for Sys.StringBuilder), and this gives a big performance improvement in my grid. I hope this helps someone.
If someone knows of a way to have better performance, do not hesitate to contact me!
History
- 1st May 2008 - First release
- 3rd May 2008 - Fixed bugs and changed article
| You must Sign In to use this message board. |
|
|
 |
|
|
 |
|
 |
Hi, Very nice code. Have you test it using the same test I did to compare the performance?
Your mind is like a parachute. It works best when open.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Yea i ran it through alot of the same kinds of tests. When i created it i thought that someone might need it, if they were using ajax, but i came to the conclusion that if you have 10000 lines to append its probably going to use alot of memory in the browser to display it, so its really not that useful clientside.
KISS "Keep It Simple, Stupid"
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
append should be :
append : function StringBuilderEx$append(text) { this._buffer[this._index] = text; this._index++; },
this._index++ missing
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, You're right! I sorry I'll fixed soon and I submit a new source code. Thank you. 
Cheer,
Gabriele.
Your mind is like a parachute. It works best when open.
|
| Sign In·View Thread·PermaLink | 3.00/5 |
|
|
|
 |
|
|
 |
|
 |
Hi,
Test on Vista with IE7:
Run Tests Append Strings (10000)...
Test concatenate strings with + : 2534 ms. Test concatenate strings with Sys.StringBuilder (append) : 315 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 220 ms.
Run Tests Append Strings with format (10000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 2075 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 787 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 580 ms.
Run Tests Append Strings (20000)...
Test concatenate strings with + : 17947 ms. Test concatenate strings with Sys.StringBuilder (append) : 628 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 441 ms.
Run Tests Append Strings with format (20000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 4089 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 1572 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 1149 ms.
Test on Vista with FF3.0b5:
Run Tests Append Strings (10000)...
Test concatenate strings with + : 33 ms. Test concatenate strings with Sys.StringBuilder (append) : 73 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 38 ms.
Run Tests Append Strings with format (10000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 1042 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 341 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 211 ms.
Run Tests Append Strings (20000)...
Test concatenate strings with + : 92 ms. Test concatenate strings with Sys.StringBuilder (append) : 173 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 75 ms.
Run Tests Append Strings with format (20000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 2724 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 726 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 450 ms.
Test on Ubuntu 8.04 with FF 3.0b5:
Run Tests Append Strings (10000)...
Test concatenate strings with + : 57 ms. Test concatenate strings with Sys.StringBuilder (append) : 54 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 18 ms.
Run Tests Append Strings with format (10000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 1253 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 407 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 228 ms.
Run Tests Append Strings (20000)...
Test concatenate strings with + : 97 ms. Test concatenate strings with Sys.StringBuilder (append) : 110 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 36 ms.
Run Tests Append Strings with format (20000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 2480 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 825 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 462 ms.
Cheers, Gabriele
Your mind is like a parachute. It works best when open.
modified on Friday, May 2, 2008 2:24 AM
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
I made the tests on a different machine:
Test on Windows XP with Firefox 2.0.0.14:
Run Tests Append Strings (10000)...
Test concatenate strings with + : 141 ms. Test concatenate strings with Sys.StringBuilder (append) : 203 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 31 ms.
Run Tests Append Strings with format (10000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 1391 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 718 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 532 ms.
Run Tests Append Strings (20000)...
Test concatenate strings with + : 266 ms. Test concatenate strings with Sys.StringBuilder (append) : 437 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 141 ms.
Run Tests Append Strings with format (20000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 2906 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 1563 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 1140 ms.
Test on Windows XP with IE6:
Run Tests Append Strings (10000)...
Test concatenate strings with + : 1671 ms. Test concatenate strings with Sys.StringBuilder (append) : 282 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 141 ms.
Run Tests Append Strings with format (10000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 1594 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 625 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 453 ms.
Run Tests Append Strings (20000)...
Test concatenate strings with + : 9797 ms. Test concatenate strings with Sys.StringBuilder (append) : 703 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 297 ms.
Run Tests Append Strings with format (20000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 3500 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 1406 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 1032 ms.
Test on Windows XP with Safari 3.1.1 (525.17)
Run Tests Append Strings (10000)...
Test concatenate strings with + : 31 ms. Test concatenate strings with Sys.StringBuilder (append) : 31 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 32 ms.
Run Tests Append Strings with format (10000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 328 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 140 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 125 ms.
Run Tests Append Strings (20000)...
Test concatenate strings with + : 62 ms. Test concatenate strings with Sys.StringBuilder (append) : 78 ms. Test concatenate strings with Megasoft78.StringBuilderEx (append): 47 ms.
Run Tests Append Strings with format (20000)...
Test concatenate strings with Sys.StringBuilder (appendFormat) : 656 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat): 297 ms. Test concatenate strings with Megasoft78.StringBuilderEx (appendFormat Split+Join): 235 ms.
Cheers,
Gabriele.
Your mind is like a parachute. It works best when open.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Perhaps these benchmarks also, you can make it part of the article body instead of having it part of the forum, which would scroll fast back after a while unless it is made sticky by the administrator.
Vasudevan Deepak Kumar Personal Homepage Tech Gossips
A pessimist sees only the dark side of the clouds, and mopes; a philosopher sees both sides, and shrugs; an optimist doesn't see the clouds at all - he's walking on them. --Leonard Louis Levinson
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Good stuff! Thanks you.
I also agree with VDK in that this would be better in a table as part of the article.
Also, can you explain why your code is faster? Seems like your technique might be a good teaching opportunity to show the rest of us some "ins-and-outs" of cross browser javascript optimization.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, Thank you. I want change my article this weekend and include tests and explanations, but it's already edited and I need to change it and send to the administrator by e-mail.
Cheers,
Gabriele.
Your mind is like a parachute. It works best when open.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|