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:
303 pushstring "new: " 305 getscopeobject 1 307 getslot 4 309 getscopeobject 1 311 getslot 3 313 subtract 314 add 315 call (1) 317 pop 318 getscopeobject 1 320 findpropstrict flash.utils::getTimer 322 callproperty flash.utils::getTimer (0) 325 convert_i 326 setslot 3 328 getscopeobject 1 330 pushbyte 0 332 setslot 5 334 jump L5 L6: 338 label 339 getscopeobject 1 341 getlex Vector 343 getlex int 345 OP_0x53 346 bkpt 347 construct (0) 349 coerce __AS3__.vec::Vector.<int> 351 setslot 6 353 getscopeobject 1 355 getslot 6 357 pushbyte 100 359 callpropvoid http://adobe.com/AS3/2006/builtin::push (1) 362 getscopeobject 1 364 getslot 6 366 pushbyte 101 368 callpropvoid http://adobe.com/AS3/2006/builtin::push (1) 371 getscopeobject 1 373 getslot 6 375 pushbyte 102 377 callpropvoid http://adobe.com/AS3/2006/builtin::push (1) 380 getscopeobject 1 382 getslot 6 384 pushbyte 103 386 callpropvoid http://adobe.com/AS3/2006/builtin::push (1) 389 getscopeobject 1 391 getslot 6 393 pushbyte 104 395 callpropvoid http://adobe.com/AS3/2006/builtin::push (1) 398 getscopeobject 1 400 getslot 5 402 increment_i 403 getscopeobject 1 405 swap 406 setslot 5Which 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/