It struck me recently that there are a lot of ways to convert variables of many types to a the String type. The ease of doing this is one of AS3’s strengths over languages where it’s error-prone, possibly insecure, and just plain difficult. The C language is the most obvious example of this and, since then, seemingly every language has enshrined string conversion in ways ranging from global String() functions (AS3) that take any variable to adding toString() to the base Object type (Java, AS3, others). AS3 seems to have chosen “all of the above” and there are now many ways to convert to a string. Below I’ll look at them from a performance standpoint and see if the everyday, run-of-the-mill boring string conversion can be improved by choosing one option over another.

Let’s review the various ways of converting to String. Let me know in the comments if I’ve missed any.

// All types
String(x);
 
// All types
""+x;
 
// All types
x.toString();
 
// int, uint, Number only
x.toString(10);
 
// Array, Vector only
x.join();
 
// Array, Vector only
s = "";
for (i = 0; i < len; ++i)
{
	s += x[i];
}

That’s six ways of converting to String, so now let’s put them to the test:

package
{
	import flash.display.*;
	import flash.events.Event;
	import flash.text.*;
	import flash.utils.*;
 
	public class StringConversionTest extends Sprite
	{
		private var logger:TextField = new TextField();
		private function log(msg:*): void { logger.appendText(msg + "\n"); }
 
		public function StringConversionTest()
		{
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			addEventListener(Event.ENTER_FRAME, testInt);
		}
 
		private function testInt(ev:Event): void
		{
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			const REPS:int = 1000000;
			const TEMP_INT:int = 44;
			log("int");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				String(TEMP_INT);
			}
			afterTime = getTimer();
			log("\tString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				""+TEMP_INT;
			}
			afterTime = getTimer();
			log("\tConcatenate: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_INT.toString();
			}
			afterTime = getTimer();
			log("\ttoString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_INT.toString(10);
			}
			afterTime = getTimer();
			log("\ttoString(10): " + (afterTime-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testInt);
			addEventListener(Event.ENTER_FRAME, testUInt);
		}
 
		private function testUInt(ev:Event): void
		{
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			const REPS:int = 1000000;
			const TEMP_UINT:uint = 44;
			log("uint");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				String(TEMP_UINT);
			}
			afterTime = getTimer();
			log("\tString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				""+TEMP_UINT;
			}
			afterTime = getTimer();
			log("\tConcatenate: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_UINT.toString();
			}
			afterTime = getTimer();
			log("\ttoString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_UINT.toString(10);
			}
			afterTime = getTimer();
			log("\ttoString(10): " + (afterTime-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testUInt);
			addEventListener(Event.ENTER_FRAME, testNumber);
		}
 
		private function testNumber(ev:Event): void
		{
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			const REPS:int = 1000000;
			const TEMP_NUMBER:Number = 44.0;
			log("Number");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				String(TEMP_NUMBER);
			}
			afterTime = getTimer();
			log("\tString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				""+TEMP_NUMBER;
			}
			afterTime = getTimer();
			log("\tConcatenate: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_NUMBER.toString();
			}
			afterTime = getTimer();
			log("\ttoString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_NUMBER.toString(10);
			}
			afterTime = getTimer();
			log("\ttoString(10): " + (afterTime-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testNumber);
			addEventListener(Event.ENTER_FRAME, testBoolean);
		}
 
		private function testBoolean(ev:Event): void
		{
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			const REPS:int = 1000000;
			const TEMP_BOOLEAN:Boolean = true;
			log("Boolean");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				String(TEMP_BOOLEAN);
			}
			afterTime = getTimer();
			log("\tString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				""+TEMP_BOOLEAN;
			}
			afterTime = getTimer();
			log("\tConcatenate: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_BOOLEAN.toString();
			}
			afterTime = getTimer();
			log("\ttoString(): " + (afterTime-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testBoolean);
			addEventListener(Event.ENTER_FRAME, testArray);
		}
 
		private function testArray(ev:Event): void
		{
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			var j:int;
			var len:int;
			var s:String;
			const REPS:int = 1000000;
			const TEMP_ARRAY:Array = [1,2,3,4,5,6,7,8,9,10];
			log("Array");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				String(TEMP_ARRAY);
			}
			afterTime = getTimer();
			log("\tString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				""+TEMP_ARRAY;
			}
			afterTime = getTimer();
			log("\tConcatenate: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_ARRAY.toString();
			}
			afterTime = getTimer();
			log("\ttoString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_ARRAY.join();
			}
			afterTime = getTimer();
			log("\tjoin(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				s = "";
				for (j = 0, len = TEMP_ARRAY.length; j < len; ++j)
				{
					s += TEMP_ARRAY[j];
				}
			}
			afterTime = getTimer();
			log("\tfor loop: " + (afterTime-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testArray);
			addEventListener(Event.ENTER_FRAME, testVector);
		}
 
		private function testVector(ev:Event): void
		{
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			var j:int;
			var len:int;
			var s:String;
			const REPS:int = 1000000;
			const TEMP_VECTOR:Vector.<int> = Vector.<int>([1,2,3,4,5,6,7,8,9,10]);
			log("Vector");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				String(TEMP_VECTOR);
			}
			afterTime = getTimer();
			log("\tString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				""+TEMP_VECTOR;
			}
			afterTime = getTimer();
			log("\tConcatenate: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_VECTOR.toString();
			}
			afterTime = getTimer();
			log("\ttoString(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				TEMP_VECTOR.join();
			}
			afterTime = getTimer();
			log("\tjoin(): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				s = "";
				for (j = 0, len = TEMP_VECTOR.length; j < len; ++j)
				{
					s += TEMP_VECTOR[j];
				}
			}
			afterTime = getTimer();
			log("\tfor loop: " + (afterTime-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testVector);
		}
	}
}

Here are the results for a 3.0 Ghz Intel Core 2 Duo on Windows XP:

Type String() Concat toString() toString(10) join() for loop
int 2 262 328 328 n/a n/a
uint 2 262 330 330 n/a n/a
Number 2 282 323 324 n/a n/a
Boolean 2 2 9 n/a n/a n/a
Array 5901 5886 5861 n/a 5507 3782
Vector (int) 5364 5379 4975 n/a 5127 3834

And here are the results for a 2.0 Ghz Intel Core 2 Duo on Mac OS X 10.5:

Type String() Concat toString() toString(10) join() for loop
int 3 465 562 562 n/a n/a
uint 3 475 560 563 n/a n/a
Number 7 499 533 537 n/a n/a
Boolean 3 3 13 n/a n/a n/a
Array 10769 10765 10677 n/a 10342 7522
Vector (int) 10828 9963 9548 n/a 9557 7478

There are a number of observations we can take away from these results:

  • The global String() conversion function is by far the fastest way of converting int, uint, and Number. For those types, the other methods are all equally terrible.
  • Concatentation (“”+x) and toString() do not have nearly the penalty with Boolean as they do with int, uint, and Number. Since Booleans can be only true or false, one would expect some decent optimization to take place. This seems to be the case with concatenation, but toString() is still 4x slower slower, probably due to the function call that has not been removed by the compiler.
  • There is little difference between the “native” ways of converting Arrays and Vectors: String(), concatentation, toString(), and join(). Sadly, the non-native approach of using a simple for loop is decidedly faster by about 40%. AS3 is outperforming native code here by a rather large margin.
  • The performance does not seem to differ between Windows and Mac OS X.

While it’s unlikely that your application will bottleneck on String conversion, you probably convert to String all the time. As a general practice of not pointlessly throwing away CPU cycles, it’s good to know which of the many options is the fastest so that you can use it routinely. While the extra typing required to make a for loop to convert an Array or Vector is more often a nuissance than a nourishment, simply using the String() conversion function instead of concatenation with an empty string or calling toString() ensures that your code will run at least 100x faster while only adding a couple of characters.