Declaring Vectors
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!
#1 by leef on June 14th, 2010 ·
Are the results consistent regardless of Vector type? What if the type is String, or Object?
#2 by jackson on June 14th, 2010 ·
Here’s what I get when using Vector.<String>:
The results are a little slower, but consistent with Vector.<int>.
#3 by Daniel Wabyick on June 14th, 2010 ·
Hey Jackson. Good test! One inconsistency I noted is that you create a fixed sized vector for your ‘scratch index’ test. If you remove that, the result is a lot slower!
If you make them all fixed size vectors, scratch index is very competitive.
#4 by jackson on June 14th, 2010 ·
Good observation! Since all of the other tests are initializing a Vector from a list of values, it made sense to allocate enough room for those values when calling the Vector‘s constructor. I thought it’d be unfair to declare an empty Vector only to grow it five times in a row. Plus, as the bytecode analysis shows, this way is exactly how the “new” syntax works.
#5 by adampasz on June 14th, 2010 ·
Thanks. I’ll take your word for it that it’s faster, but I like the syntax much better without the “Vector.”!
#6 by katopz on June 14th, 2010 ·
i got some interesting result here
new []; // use 1209
new Vector.(); // use 1190
so without push anything, “new Vector.();” still faster?
#7 by jackson on June 14th, 2010 ·
I assume your post got mangled by my posting system. It’s a pain, but you have to use < and > for your Vector types.
Anyhow, I also assume that those two example lines would generate identical bytecode. Since the “new” operator (for lack of a better term) results in allocating a Vector then assigning values to it via indexing (see article), I’d assume that using the “new” operator without any elements would simply amount to calling the Vector constructor with a size of zero and then not assigning any values to it via indexing.
You may still get a little bit of wiggle in your test numbers. Try it over and over and you should see they’re statistically equivalent.
#8 by katopz on June 15th, 2010 ·
actually i apply new <int>[]; to whole away3dlite libs and expect faster result but eventually i drop 1fps after that,
so i get back and test against new Vector<int>(); btw no “push” in my case just new,and result is a bit slower and never equal or higher, (i did test 5 times for this and 2 times for whole libs)
then look like this trick won’t fit in my case, thx for sharing anyway ;)
#9 by pleclech on June 15th, 2010 ·
The compiler didn’t generate a bkpt, it’s only your decompiler who doesn’t know how to decode the applytype (0x53) instruction
which take as parameter how many params there are on the stack. Since you are creating a Vector of int there is only one param (int), which turn into 0x53 0x01 (=> 0x01 is also the opcode for bkpt).
Patrick.
#10 by jackson on June 15th, 2010 ·
I figured something was fishy here. Can you recommend an alternate disassembler that understands these instructions better?
#11 by pleclech on June 15th, 2010 ·
You can use the dump tool within Apparat by Joa Ebert at http://code.google.com/p/apparat/
#12 by jackson on June 15th, 2010 ·
Cool. I’ll give it a try!
#13 by Mario Klingemann on June 15th, 2010 ·
That’s a very hand find, thanks! Did you already check if using a ByteArray instead of an Array works, too (given that you want to create a int or uint Vector from it?
#14 by jackson on June 15th, 2010 ·
I’m not sure what you mean. Do you know of special syntax to declare a ByteArray?
#15 by nick on June 16th, 2010 ·
anything other than Vector. does not compile for me in flex 3.5
Is this a flex 4 only thing?
-ndy
#16 by jackson on June 16th, 2010 ·
Yep, it’s Flex 4 only according to Adobe’s documentation.
#17 by Fumio Nonaka on June 17th, 2010 ·
To make test fair, each Vector instance should be assigned to a variable (v). Then the “new” seems to be almost equivalent to the “scratch many”.
http://wonderfl.net/c/4aBs
#18 by jackson on June 17th, 2010 ·
I looked at the version you posted to wonderfl in nemo440 and it seems like your compiler generated a push for each element, similar to the “push single” test:
Which version of Flex were you compiling with? For reference, I used MXMLC 4.0.0.14159.
#19 by Fumio Nonaka on June 19th, 2010 ·
They said Version 4.0.0 build 14159.
http://wonderfl.net/help#help_compiler
And the result of “new” is faster than “push single” and nearly equal to “scratch many”.
#20 by whitered on October 7th, 2010 ·
There is a strange effect with increasing vector’s length
http://pastebin.com/27qGJa2X
Can anybody explain this?
#21 by whitered on October 7th, 2010 ·
the answer was found here: http://jpauclair.net/2009/12/05/tamarin-part-ii-more-on-array-and-vector/