The Four Vector Classes
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); } } }
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!
#1 by Tommy on January 6th, 2014 ·
oh yeah i remember that, I only wanted to use 2 boolean vectors for a 4 cases pathing map, and I realized these were taking as much as int vectors, so I used a vector with bitwise operations instead.
#2 by Vjekoslav Ratkajec on January 7th, 2014 ·
Just a quick note:
in last test you are outputting
vecInt
instead ofvecBoolean
:row("Boolean", getSize(vecInt));
P.S. Thanks for great posts!
Best,
Vjeko
#3 by jackson on January 7th, 2014 ·
Thank you very much for spotting this typo. The true size is actually 64 bits per element since each element is a 64 bit pointer (in my Flash Player). I’ve updated the article.