The differences between Vector and Array have been quite interesting since Vector was introduced in Flash Player 10. Until just recently I didn’t know that there was special syntax for declaring a Vector akin to Array's special a = [1,2,3,4,5] trick. This got me thinking about the various ways one can declare a Vector and, of course, how they’re implemented in bytecode and what the speed differences, if any, are. Read on for some nitty gritty about how you declare Vectors in AS3.

The syntax I’ve found for declaring Vectors is actually pretty simple: v = new <int>[1,2,3,4,5]. This is preferable to the longer version I had been using: v = Vector.<int>([1,2,3,4,5]). That version uses Array's special syntax to create an Array and then passes it to the Vector.<int>() function.

Besides the creation of the temporary Array as garbage and the extra characters to type, it’s quite error prone as I and others I’ve seen make this mistake: v = new Vector.<int>([1,2,3,4,5]). The difference is that simply adding the new keyword means you’re no longer calling the Vector.<int>() function to convert an Array to a Vector, but instead calling the Vector constructor and passing an Array for its size. MXMLC will not give you a compiler error or warning, but instead let you go on to execute that code. At run time you will not get an error either as the Array is simply treated as through you had passed zero and a Vector of size zero is constructed.

So far this new (to me) syntax seems great on all fronts. But what bytecode do you get when compiling it with MXMLC? To test, I made a simple class that does nothing but try out these methods:

package
{
	import flash.display.*;
 
	public class DeclaringVectors extends Sprite
	{
		private function declareCast(): void
		{
			Vector.<int>([100,101,102,103,104]);
		}
 
		private function declareNew(): void
		{
			new <int>[100,101,102,103,104];
		}
 
		private function declareScratchSingle(): void
		{
			var v:Vector.<int> = new Vector.<int>();
			v.push(100);
			v.push(101);
			v.push(102);
			v.push(103);
			v.push(104);
		}
 
		private function declareScratchMany(): void
		{
			var v:Vector.<int> = new Vector.<int>();
			v.push(100,101,102,103,104);
		}
 
		private function declareScratchIndex(): void
		{
			var v:Vector.<int> = new Vector.<int>(5);
			v[0] = 100;
			v[1] = 101;
			v[2] = 102;
			v[3] = 103;
			v[4] = 104;
		}
	}
}

Each of these methods creates a Vector with the same five integers. Let’s look at the generated bytecode for the old cast method (declareCast):

function private::declareCast():void	/* disp_id 0*/
  {
    // local_count=1 max_scope=1 max_stack=7 code_len=25
    0       getlocal0     	
    1       pushscope     	
    2       getlex        	Vector
    4       getlex        	int
    6       OP_0x53       	
    7       bkpt          	
    8       getglobalscope	
    9       pushbyte      	100
    11      pushbyte      	101
    13      pushbyte      	102
    15      pushbyte      	103
    17      pushbyte      	104
    19      newarray      	[5]
    21      call          	(1)
    23      pop           	
    24      returnvoid    	
  }

According to this helpful answer on StackOverflow, OP_0x53 is the bytecode for making a new generic type from the top couple of elements on the stack. In this case, it’s making a type of Vector.<int>. Oddly, even though this is a release build, MXMLC generated a bkpt instruction, which means to break into the debugger if it is active. Then the integers are made into an Array using newarray and the Vector.<int> is called with the Array as its parameter. This is pretty much a straightforward translation of the AS3 into bytecode, so let’s see if the new method is:

  function private::declareNew():void	/* disp_id 0*/
  {
    // local_count=1 max_scope=1 max_stack=4 code_len=51
    0       getlocal0     	
    1       pushscope     	
    2       findpropstrict	Vector
    4       getproperty   	Vector
    6       getlex        	int
    8       OP_0x53       	
    9       bkpt          	
    10      pushbyte      	5
    12      construct     	(1)
    14      dup           	
    15      pushbyte      	0
    17      pushbyte      	100
    19      setproperty   	null
    21      dup           	
    22      pushbyte      	1
    24      pushbyte      	101
    26      setproperty   	null
    28      dup           	
    29      pushbyte      	2
    31      pushbyte      	102
    33      setproperty   	null
    35      dup           	
    36      pushbyte      	3
    38      pushbyte      	103
    40      setproperty   	null
    42      dup           	
    43      pushbyte      	4
    45      pushbyte      	104
    47      setproperty   	null
    49      pop           	
    50      returnvoid    	
  }

This new method looks much less straightforward! The first part is the same, but after the mysterious breakpoint the Vector is constructed with a length of five. The five integers are then assigned to their appropriate indices in the Vector using the setproperty instruction, which is equivalent to the [] operator. What’s notable about this approach is that it isn’t just syntax sugar as it really doesn’t use an Array as a middleman like the “cast” version does above. Now let’s look at the versions where we don’t use any special syntax and instead create our Vector “from scratch”:

  function private::declareScratchSingle():void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=44
    0       getlocal0     	
    1       pushscope     	
    2       getlex        	Vector
    4       getlex        	int
    6       OP_0x53       	
    7       bkpt          	
    8       construct     	(0)
    10      coerce        	__AS3__.vec::Vector.<int>
    12      setlocal1     	
    13      getlocal1     	
    14      pushbyte      	100
    16      callpropvoid  	http://adobe.com/AS3/2006/builtin::push (1)
    19      getlocal1     	
    20      pushbyte      	101
    22      callpropvoid  	http://adobe.com/AS3/2006/builtin::push (1)
    25      getlocal1     	
    26      pushbyte      	102
    28      callpropvoid  	http://adobe.com/AS3/2006/builtin::push (1)
    31      getlocal1     	
    32      pushbyte      	103
    34      callpropvoid  	http://adobe.com/AS3/2006/builtin::push (1)
    37      getlocal1     	
    38      pushbyte      	104
    40      callpropvoid  	http://adobe.com/AS3/2006/builtin::push (1)
    43      returnvoid    	
  }
  function private::declareScratchMany():void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=6 code_len=28
    0       getlocal0     	
    1       pushscope     	
    2       getlex        	Vector
    4       getlex        	int
    6       OP_0x53       	
    7       bkpt          	
    8       construct     	(0)
    10      coerce        	__AS3__.vec::Vector.<int>
    12      setlocal1     	
    13      getlocal1     	
    14      pushbyte      	100
    16      pushbyte      	101
    18      pushbyte      	102
    20      pushbyte      	103
    22      pushbyte      	104
    24      callpropvoid  	http://adobe.com/AS3/2006/builtin::push (5)
    27      returnvoid    	
  }
 
  function private::declareScratchIndex():void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=3 code_len=51
    0       getlocal0     	
    1       pushscope     	
    2       getlex        	Vector
    4       getlex        	int
    6       OP_0x53       	
    7       bkpt          	
    8       pushbyte      	5
    10      construct     	(1)
    12      coerce        	__AS3__.vec::Vector.<int>
    14      setlocal1     	
    15      getlocal1     	
    16      pushbyte      	0
    18      pushbyte      	100
    20      setproperty   	null
    22      getlocal1     	
    23      pushbyte      	1
    25      pushbyte      	101
    27      setproperty   	null
    29      getlocal1     	
    30      pushbyte      	2
    32      pushbyte      	102
    34      setproperty   	null
    36      getlocal1     	
    37      pushbyte      	3
    39      pushbyte      	103
    41      setproperty   	null
    43      getlocal1     	
    44      pushbyte      	4
    46      pushbyte      	104
    48      setproperty   	null
    50      returnvoid    	
  }

All of these are quite straightforward. In the versions using Vector.push(), the Vector is constructed and the integers are simply put in the array via Vector.push() one at a time or all at once. The version using the index operator ([]) is notable since it is the exact same bytecode as the version using the new, convenient operator.

So we have five ways to make a Vector. How do they perform? Let’s do a quick test:

package
{
	import flash.text.*;
	import flash.utils.*;
	import flash.display.*;
 
	public class DeclaringVectors extends Sprite
	{
		public function DeclaringVectors()
		{
			var logger:TextField = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
			function log(msg:*): void { logger.appendText(msg + "\n"); }
 
			var beforeTime:int;
			var afterTime:int;
			var i:int;
			var v:Vector.<int>;
			const REPS:int = 1000000;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				Vector.<int>([100,101,102,103,104]);
			}
			afterTime = getTimer();
			log("cast: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				new <int>[100,101,102,103,104];
			}
			afterTime = getTimer();
			log("new: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v = new Vector.<int>();
				v.push(100);
				v.push(101);
				v.push(102);
				v.push(103);
				v.push(104);
			}
			afterTime = getTimer();
			log("scratch single: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v = new Vector.<int>();
				v.push(100,101,102,103,104);
			}
			afterTime = getTimer();
			log("scratch many: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				v = new Vector.<int>(5);
				v[0] = 100;
				v[1] = 101;
				v[2] = 102;
				v[3] = 103;
				v[4] = 104;
			}
			afterTime = getTimer();
			log("scratch index: " + (afterTime-beforeTime));
		}
	}
}

Since Flash Player 10.1 is now the current version, I’m using it for this test and all future tests. Here are the results:

Environment Cast New Scratch (Single Push) Scratch (Many Push) Scratch (Index)
3.0 Ghz Intel Core 2 Duo, Windows XP 1024 331 791 363 331
2.0 Ghz Intel Core 2 Duo, Mac OS X 1762 624 1293 678 624

First off there is no significant performance difference between Windows XP and Mac OS X. Secondly, there are two clear performance losers: the cast version is worst of all and the version using one push at a time is not far behind. The version doing a single push is, as the bytecode would suggest, quite a bit quicker and actually comes close to the best version. Again as the bytecode suggests, in this case by being absolutely identical, the new operator and the version using the index operator perform identically. Since the new operator requires the least typing, doesn’t use a temporary Array and the garbage it leaves behind, and, to me at least, is quite a bit cleaner and less error prone, it is good to see that it is also the fastest. For me, the new operator is a very nice find. I hope you all enjoy it just as much!