450x Speed Up Copying Between Collections
Programming in AS3 invariably involves choosing between various collections: Array
, Vector
, Dictionary
, Object
, ByteArray
, and so on. What if you need to quickly copy between them? Your choice of collection could result in a 450x slowdown in your app… or a 450x speedup!
The following test fills all collections with 10,000 integers and then copies 10,000 integers 1,000 times between two of the same collection types:
Array
Vector
Dictionary
(weak keys)Dictionary
(strong keys)Object
ByteArray
All of them except ByteArray
use a simple collection2[i] = collection[i]
loop. In the case of ByteArray
, bytes are copied en masse using ByteArray.readBytes
and a couple of position
setters.
package { import flash.text.TextField; import flash.utils.getTimer; import flash.utils.ByteArray; import flash.utils.Dictionary; import flash.display.Sprite; public class CopyingBetweenCollections extends Sprite { public function CopyingBetweenCollections() { var SIZE:int = 10000; var REPS:int = 1000; var i:int; var j:int; var beforeTime:int; var afterTime:int; var array:Array = []; var vector:Vector.<int> = new <int>[]; var dictionaryWeak:Dictionary = new Dictionary(true); var dictionaryStrong:Dictionary = new Dictionary(false); var object:Object = {}; var byteArray:ByteArray = new ByteArray(); var array2:Array = []; var vector2:Vector.<int> = new <int>[]; var dictionaryWeak2:Dictionary = new Dictionary(false); var dictionaryStrong2:Dictionary = new Dictionary(true); var object2:Object = {}; var byteArray2:ByteArray = new ByteArray(); var tf:TextField = new TextField(); tf.width = stage.stageWidth; tf.height = stage.stageHeight; tf.text = "Collection,Time\n"; addChild(tf); byteArray.length = SIZE; for (i = 0; i < SIZE; ++i) { array[i] = i; vector[i] = i; dictionaryWeak[i] = i; dictionaryStrong[i] = i; object[i] = i; byteArray[i] = i; array2[i] = i; vector2[i] = i; dictionaryWeak2[i] = i; dictionaryStrong2[i] = i; object2[i] = i; byteArray2[i] = i; } beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for (j = 0; j < SIZE; ++j) { array2[j] = array[j]; } } afterTime = getTimer(); tf.appendText("Array," + (afterTime-beforeTime) + "\n"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for (j = 0; j < SIZE; ++j) { vector2[j] = vector[j]; } } afterTime = getTimer(); tf.appendText("Vector," + (afterTime-beforeTime) + "\n"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for (j = 0; j < SIZE; ++j) { dictionaryWeak2[j] = dictionaryWeak[j]; } } afterTime = getTimer(); tf.appendText("Dictionary (weak)," + (afterTime-beforeTime) + "\n"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for (j = 0; j < SIZE; ++j) { dictionaryStrong2[j] = dictionaryStrong[j]; } } afterTime = getTimer(); tf.appendText("Dictionary (strong)," + (afterTime-beforeTime) + "\n"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for (j = 0; j < SIZE; ++j) { object2[j] = object[j]; } } afterTime = getTimer(); tf.appendText("Object," + (afterTime-beforeTime) + "\n"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { byteArray.position = 0; byteArray2.position = 0; byteArray2.readBytes(byteArray, 0, SIZE); } afterTime = getTimer(); tf.appendText("ByteArray," + (afterTime-beforeTime) + "\n"); } } }
I ran this test in the following environment:
- Release version of Flash Player 11.7.700.169
- 2.3 Ghz Intel Core i7
- Mac OS X 10.8.3
- ASC 2.0 build 352231 (
-debug=false -verbose-stacktraces=false -inline
)
And here are the results I got:
Collection | Time |
---|---|
Array | 154 |
Vector | 26 |
Dictionary (weak) | 708 |
Dictionary (strong) | 706 |
Object | 682 |
ByteArray | 1 |
Dictionary
(regardless of key strength) and Object
are far and away the slowest collections to read and write from. Interestingly, Object
is actually faster than Dictionary
despite needing to translate int
values to String
keys. Array
is about 3x faster than either of these, but still solidly in the “slow” camp compared to Vector
and ByteArray
.
Vector
is about 6x faster than Array
and 18x faster than Dictionary
or Object
, but it can’t touch ByteArray
with its barely-measurable time of 1 millisecond. There were many cases while running the tests that it would register as 0 milliseconds, but the basic idea stands: it’s at least 25x faster than Vector
, 150x faster than Array
, and 450x faster than Object
or Dictionary
.
So if you need fast copying between collections, use a ByteArray
. Second-best is a Vector
if ByteArray
offers too many downsides (e.g. it’s cumbersome).
Spot a bug? Have a question or suggestion? Post a comment!
#1 by AlexG on April 22nd, 2013 ·
Great results, but did you test measuring the time on ByteArray.readObject() which takes some extra time on serializing the Object into bytes. Also did you count ByteArray.writeObject() which also takes time to convert bytes to Object ? I think these are important from practical point of view.
I would suggest this code
for (i = 0; i < SIZE; ++i) {
object[i] = i
}
beforeTime = getTimer()
for (i = 0; i < REPS; ++i){
byteArray.writeObject(object)
byteArray.position = 0;
byteArray2.position = 0;
byteArray2.readBytes(byteArray, 0, SIZE);
var object_2:Object = byteArray2.readObject()
}
afterTime = getTimer()
#2 by jackson on April 22nd, 2013 ·
I thought about using some kind of objects, but it wouldn’t be fair to
ByteArray
sinceByteArray
would be serializing/deserializing and therefore making true copies while the others would simply be copying a pointer/reference to the same object.#3 by ben w on April 22nd, 2013 ·
A fast little function then! If you change the number I wonder at what point they would start to converge. i.e. SIZE = 1000 and REPS = 10000 or SIZE = 100 and REPS = 100000
My guess would be for really small collections they would balance out a bit more but on the bigger ones BA is gonna smash their face in, as it is in your tests.
Interesting find JD.
#4 by jackson on April 22nd, 2013 ·
There would definitely be a convergence at some point. The function call overhead for
readBytes
and theposition
setters will start to be larger and larger as you reduceSIZE
. I’m not sure where the convergence is, but the difference was still quite large whenSIZE
was 1,000.#5 by Bob on April 26th, 2013 ·
It looks at a quick glance that you stacked the odds in favor of ByteArray. I would assume tts best at sequential read and write. Random access would a more telling test IMHO of real world use.
#6 by Benny on February 20th, 2014 ·
Hey,
did you tried slice. looks like that function is way faster than an for loop copy.
Result is:
28 testSlice10000000
667 testForCopy10000000
#7 by jackson on February 21st, 2014 ·
slice()
is slightly—but importantly—different than what’s being tested in the article. It’ll create a whole newArray
orVector
rather than copying to an existing one. Still, thanks for providing your test numbers. Perhaps I’ll do a followup article that broadens the scope of this article on copying.