In the last post on string performance, we did an analysis of string performance that spanned all of the major browsers, with the goal of optimizing the performance of the dojox.string.Builder. While we were able to create significant improvements in performance—particularly with Firefox—the performance under Internet Explorer was still pretty poor compared to native methods.

The goal for this article was to bring Builder’s performance down to comparable native operations—and we were able to do with through a combination of a slight change in code with using different ways of calling the append method.

Dispelling the myth of Array.join

Past wisdom has said that for performance reasons it is better to push string fragments into an array, and then join them together at the last minute. This idea was based on the poor string performance present since at least IE3 and in part on the memory consumption large object operations tends to have, due to the simplistic mark-and-sweep generational garbage collector used for the JScript engine—admirably demonstrated by this article on JScript garbage collector written by Dan Pupius.

However, the previous string performance analysis revealed an interesting tidbit: when appending string fragments via iteration, the performance of the “+=” operator was actually better in Internet Explorer 7 than pushing fragments into an array and joining them. With this in mind, the first thing we did was to remove the IE-only branch from the source code of the Builder, so that instead of using an array as an internal buffer, it used the same string that all of the other browsers do. From here, we tested performance with both Internet Explorer 6 and Internet Explorer 7:

Internet Explorer 6 Builder branch

Internet Explorer 7 Builder branch

The first thing we notice is that with IE6, using an array as a buffer is slightly faster—but we can also notice that the difference is negligible. We also notice here that with IE7, the performance of using a string as an internal buffer is better than using an array. But these tests show another interesting tidbit: the performance in both browsers is better when passing more than one argument to the Builder.append method.

(For a quick refresher: the dojox.string.Builder object’s primary method is append, which was written to take N arguments; the three tests here are when a builder is reused and one argument is appended at a time, a new builder is used and 2 arguments are passed, and a new builder is used on one argument is passed.)

Based on this data, we removed the IE-specific branch from Builder, so that there is only the internal string as a buffer.

To append or not to append, that is the question.

Following the last observation, a quick battery of tests were undertaken to see exactly what kind of performance improvement there would be when passing more than one argument. In particular, we wanted to see what the effects of passing anywhere from 1 to 9 arguments (9 being the arbitrary optimization limit we wrote during the course of the last analysis).

As a reminder, this is the optimization we wrote for Firefox during the last analysis:

append: function(s){ 
	if(arguments.length>1){
		var tmp="", l=arguments.length;
		switch(l){
			case 9: tmp=arguments[8]+tmp;
			case 8: tmp=arguments[7]+tmp;
			case 7: tmp=arguments[6]+tmp;
			case 6: tmp=arguments[5]+tmp;
			case 5: tmp=arguments[4]+tmp;
			case 4: tmp=arguments[3]+tmp;
			case 3: tmp=arguments[2]+tmp;
			case 2: {
				this.b+=arguments[0]+arguments[1]+tmp;
				break;
			}
			default: {
				var i=0, tmp="";
				while(i < arguments.length){
					tmp += arguments[i++];
				}
				this.b += tmp;
			}
		}
	} else {
		this.b += s;
	}
	return this;
}

(Note: we added another slight optimization for the case of more than 9 arguments by creating a temporary string buffer and then adding the results to the real buffer.)

To test, we created alternate versions of the builderForMulti test that passed N arguments at a time. To ensure the results of the tests were accurate, we had to follow each loop with a remainder loop (which introduced a slight noise factor). Here are the results, for both IE6 and IE7:

IE6, multiple arguments to Builder.append

IE7, passing multiple arguments to Builder.append

As you can see, the results in performance are dramatic; passing only 3 arguments at a time cut the performance almost in half, and passing 9 cut the performance time in both versions of Internet Explorer by close to a factor of 4.

So what does this mean?

We think the key to this performance improvement has less to do the number of arguments and more to do with the way the previous optimization works; because we used a temporary string buffer to put all of the passed arguments together before appending it to the main string buffer, we minimize the effect of large string copying operations—which has been one of the major issues with performance with Internet Explorer.

In addition, being able to cut the number of loops made in each test makes a significant difference; we know from previous tests that loop overhead in IE tends to be a little higher than other browsers.

Comparing the results with Array.join.

Finally, we compare the performance numbers of passing 5 or more arguments to Builder.append against the joinFor numbers from the previous analysis:

Array.join() vs. Builder with 5 or more args

As you can see here, passing 7 to 9 arguments has the effect of being faster performance-wise than pushing fragments one at a time into an array and joining them. Passing 5 or 6 arguments is slightly slower but comparable enough (given the Monte Carlo nature of the tests) to warrant consideration for usage.

Results, conclusions and other considerations.

First things first—with the performance improvements with IE7, we no longer need to consider using an alternate path when doing large scale string operations; using Array.join in an iterative situation gives you no major advantages than using += in the same situation. In addition, the differences with IE6 were slight enough to allow you to not bother forking for that specific version.

The only time considering using an array as opposed to a string for these kind of operations is when you are aware that the fragments you are appending are very large (on the order of > 65536 bytes); doing this will cause the GC issues Dan talks about in his analysis of object allocation and the JScript garbage collector.

From there, we can progress to programming techniques—with Internet Explorer, it is much better to call Builder.append with as many arguments as possible than to simply iterate and push things in one at a time.

It is also better to start small; try to structure your string operations so that very large string operations are minimized. In this case, using a temporary buffer to assemble a set of strings together and then adding them to a much larger string is better than constantly adding small fragments to a larger string.

And as always, minimizing the size of an iteration will help get extra performance out of JScript.

Other browsers.

While we did not record any numbers with other browsers, we did test the multiple argument approach with Firefox, Safari and Opera on both OS X and Windows 2003 Server. Passing as many arguments as possible did increase performance results with both Safari and Opera—though because both browsers have such good performance, the differences were much smaller than with Internet Explorer.

An interesting thing though—passing multiple arguments with Firefox did not increase performance at all. In fact, the numbers remained flat, no matter how many arguments were passed.

As with the last analysis, here are the raw numbers.