There are four Vector classes in AS3. It seems like there is only one—Vector—and that it supports generics, but that is only an illusion. Today’s article will do some tests to reveal the implications to your app’s correctness and efficiency.

Flash Player’s AVM2 virtual machine that runs AS3 doesn’t support generics, so the Vector class is implemented as four classes behind the scenes:

  • Vector.<int>
  • Vector.<uint>
  • Vector.<Number>
  • Vector.<*>

The last one (Vector.<*>) handles all the Vector objects where the elements are not one of the first three types: int, uint, and Number. In those three cases there are special versions of the Vector class that more efficiently store the contents of the Vector.

The following little app uses describeType to see which Vector class is being used for some common types:

package
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.describeType;
 
	public class VectorClasses extends Sprite
	{
		private var logger:TextField = new TextField();
		private function row(...cols): void
		{
			logger.appendText(cols.join(",")+"\n");
		}
 
		public function VectorClasses()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
 
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			var vecInt:Vector.<int> = new <int>[];
			var vecUInt:Vector.<uint> = new <uint>[];
			var vecBoolean:Vector.<Boolean> = new <Boolean>[];
			var vecNumber:Vector.<Number> = new <Number>[];
			var vecObject:Vector.<Object> = new <Object>[];
			var vecString:Vector.<String> = new <String>[];
			var vecArray:Vector.<Array> = new <Array>[];
			var vecVecInt:Vector.<Vector.<int>> = new <Vector.<int>>[];
			var vecXML:Vector.<XML> = new <XML>[];
 
			row("Vector Type", "Class");
			whichVector(vecInt, "int");
			whichVector(vecUInt, "uint");
			whichVector(vecBoolean, "Boolean");
			whichVector(vecNumber, "Number");
			whichVector(vecObject, "Object");
			whichVector(vecString, "String");
			whichVector(vecArray, "Array");
			whichVector(vecVecInt, "Vector.<int>");
			whichVector(vecXML, "XML");
		}
 
		private function whichVector(vec:*, type:String): void
		{
			var description:XML = describeType(vec);
			var base:String = description.@base;
			var name:String = description.@name;
			var clazz:String = base != "Object" ? base : name;
			row(type, clazz);
		}
	}
}

Here’s what it outputs:

Vector Type Class
int __AS3__.vec::Vector.
uint __AS3__.vec::Vector.
Boolean __AS3__.vec::Vector.<*>
Number __AS3__.vec::Vector.
Object __AS3__.vec::Vector.<*>
String __AS3__.vec::Vector.<*>
Array __AS3__.vec::Vector.<*>
Vector. __AS3__.vec::Vector.<*>
XML __AS3__.vec::Vector.<*>

At this point you may be asking yourself how this affects your AS3 programming. Under many circumstances, it won’t affect it at all. However, there are some cases where it can have a major effect. For example, notice that there is no special case Vector.<Boolean> class. This means that a Vector.<Boolean> is actually a Vector.<*>. In turn, this means that all accesses to the elements of that Vector.<Boolean> are treated as accesses to untyped (*) variables and a runtime conversion is applied. This is slow as the following test shows:

package
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.getTimer;
 
	public class UntypedVector extends Sprite
	{
		private var logger:TextField = new TextField();
		private function row(...cols): void
		{
			logger.appendText(cols.join(",")+"\n");
		}
 
		public function UntypedVector()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
 
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			test();
		}
 
		private function test(): void
		{
			var vecInt:Vector.<int> = new <int>[1];
			var vecBoolean:Vector.<Boolean> = new <Boolean>[true];
			var REPS:int = 1000000000;
			var i:int;
			var before:int;
			var after:int;
			var tempInt:int;
			var tempBoolean:Boolean;
 
			row("Vector Type", "Time");
 
			before = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				tempInt = vecInt[0];
			}
			after = getTimer();
			row("int", (after-before));
 
			before = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				tempBoolean = vecBoolean[0];
			}
			after = getTimer();
			row("Boolean", (after-before));
 
			row("\n\n\n\n",tempBoolean,tempInt);
		}
	}
}

Run the test

I ran this test in the following environment:

  • Release version of Flash Player 11.9.900.170
  • 2.3 Ghz Intel Core i7
  • Mac OS X 10.9.1
  • Google Chrome 31.0.1650.63
  • ASC 2.0.0 build 354071 (-debug=false -verbose-stacktraces=false -inline -optimize=true)

And here are the results I got:

Vector Type Time
int 1919
Boolean 1935

It’s a small slowdown, but it’s consistently there with each run of the test app.

The Boolean elements will also not be stored using one bit or even one byte per element. Instead, it takes 64 bits (8 bytes) per element just the same as with * because Vector.<*> is used and the size of a pointer on my 64 bit Flash Player is 64 bits (8 bytes). The following test app confirms this:

package
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.sampler.getSize;
 
	public class UntypedVectorSize extends Sprite
	{
		private var logger:TextField = new TextField();
		private function row(...cols): void
		{
			logger.appendText(cols.join(",")+"\n");
		}
 
		public function UntypedVectorSize()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
 
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			var SIZE:int = 1000000;
			var vecInt:Vector.<int> = new Vector.<int>(SIZE);
			var vecUInt:Vector.<uint> = new Vector.<uint>(SIZE);
			var vecNumber:Vector.<Number> = new Vector.<Number>(SIZE);
			var vecBoolean:Vector.<Boolean> = new Vector.<Boolean>(SIZE);
 
			row("Vector Type", "Size (per million)");
			row("int", getSize(vecInt));
			row("uint", getSize(vecUInt));
			row("Number", getSize(vecNumber));
			row("Boolean", getSize(vecBoolean));
		}
	}
}

Which outputs:

Vector Type Size (per million)
int 4001896
uint 4001896
Number 8003688
Boolean 8003688

Finally, these different Vector classes can affect the execution of your code. For example, the is operator used to determine which type a variable is reveals the hidden Vector classes. Consider this simple code:

var vecInt:Vector.<int> = new Vector.<int>(10);
if (vecInt is Vector.<*>)
    trace("IS");
else
    trace("IS NOT");

Since * can handle the int type, you might expect “IS” to be printed. However, since Vector.<int> is its own class and totally unrelated to the Vector.<*> class, this code prints “IS NOT”. So if you’re going to be dynamically handling any vectors, consider checking for all four Vector classes like so:

function isVector(v:*): Boolean
{
    return v is Vector.<*>
           || v is Vector.<int>
           || v is Vector.<uint>
           || v is Vector.<Number>;
}

Spot a bug? Have a question or suggestion? Post a comment!