Accessing Objects
There are three main ways to access the contents of objects in AS3: the dot (.
) operator, the index ([]
) operator, and the in
operator. The first two are well known and functionally-equivalent because obj.property
evaluates to the same value as obj["property"]
. The in
operator is different as I’ve described before: it returns a Boolean
indicating whether or not the object has the given property. There are a lot of cases—error checking, for example—where we only care if an object has a property and not what that property is. So, can we improve performance by using the is
operator rather than the index or dot operators? (UPDATE: hasOwnProperty results added)
The index ([]
) and in
operators apply to many types of objects in AS3. In fact, I even wrote an article titled Indexing Anything last year that talked about how you can use the index operator on pretty much anything. Well, the same applies to the in
operator. The dot operator, however, undergoes more compile-time checks and therefore can’t be applied to so many objects. Regardless, for today’s performance test we’ll look at how these operators perform on a variety of objects: Array
, Vector
, Object
, Dictionary
, and class instances wherever applicable. It may seem a bit strange to apply the in
operator to Array
and Vector
, but AS3 treats their indices as properties. For example, an Array
of one element returns true
for 0 in arr
and false
for 1 in arr
. With that in mind, have a look at the performance test:
package { import flash.display.*; import flash.text.*; import flash.utils.*; public class AccessingObjects extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } public function AccessingObjects() { __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); var beforeTime:int; var afterTime:int; var i:int; const REPS:int = 1000000; var a:Array = ["bar"]; var v:Vector.<String> = new <String>["bar"]; var o:Object = {foo:"bar"}; var d:Dictionary = new Dictionary(); d.foo = "bar"; var c:MyClass = new MyClass(); log("Array"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { 0 in a; } afterTime = getTimer(); log("\t0 in a: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { 1 in a; } afterTime = getTimer(); log("\t1 in a: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { a[0]; } afterTime = getTimer(); log("\ta[0]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { a[1]; } afterTime = getTimer(); log("\ta[1]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { a.hasOwnProperty("0"); } afterTime = getTimer(); log("\ta.hasOwnProperty(\"0\"): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { a.hasOwnProperty("1"); } afterTime = getTimer(); log("\ta.hasOwnProperty(\"1\"): " + (afterTime-beforeTime)); log("Vector"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { 0 in v; } afterTime = getTimer(); log("\t0 in v: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { try{1 in v;}catch(err:Error){} } afterTime = getTimer(); log("\t1 in v: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { v[0]; } afterTime = getTimer(); log("\tv[0]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { try{v[1];}catch(err:Error){} } afterTime = getTimer(); log("\tv[1]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { v.hasOwnProperty("0"); } afterTime = getTimer(); log("\tv.hasOwnProperty(\"0\"): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { v.hasOwnProperty("1"); } afterTime = getTimer(); log("\tv.hasOwnProperty(\"1\"): " + (afterTime-beforeTime)); log("Object"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { "foo" in o; } afterTime = getTimer(); log("\t\"foo\" in o: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { "goo" in o; } afterTime = getTimer(); log("\t\"goo\" in o: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { o["foo"]; } afterTime = getTimer(); log("\to[\"foo\"]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { o["goo"]; } afterTime = getTimer(); log("\to[\"goo\"]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { o.foo; } afterTime = getTimer(); log("\to.foo: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { o.goo; } afterTime = getTimer(); log("\to.goo: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { o.hasOwnProperty("0"); } afterTime = getTimer(); log("\to.hasOwnProperty(\"0\"): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { o.hasOwnProperty("1"); } afterTime = getTimer(); log("\to.hasOwnProperty(\"1\"): " + (afterTime-beforeTime)); log("Dictionary"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { "foo" in d; } afterTime = getTimer(); log("\t\"foo\" in d: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { "goo" in d; } afterTime = getTimer(); log("\t\"goo\" in o: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { d["foo"]; } afterTime = getTimer(); log("\td[\"foo\"]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { d["goo"]; } afterTime = getTimer(); log("\td[\"goo\"]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { d.foo; } afterTime = getTimer(); log("\td.foo: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { d.goo; } afterTime = getTimer(); log("\td.goo: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { d.hasOwnProperty("0"); } afterTime = getTimer(); log("\td.hasOwnProperty(\"0\"): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { d.hasOwnProperty("1"); } afterTime = getTimer(); log("\td.hasOwnProperty(\"1\"): " + (afterTime-beforeTime)); log("Instance"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { "foo" in c; } afterTime = getTimer(); log("\t\"foo\" in c: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { "goo" in c; } afterTime = getTimer(); log("\t\"goo\" in c: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { c["foo"]; } afterTime = getTimer(); log("\tc[\"foo\"]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { try{c["goo"];}catch(err:Error){} } afterTime = getTimer(); log("\tc[\"goo\"]: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { c.foo; } afterTime = getTimer(); log("\tc.foo: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { c.hasOwnProperty("0"); } afterTime = getTimer(); log("\tc.hasOwnProperty(\"0\"): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { c.hasOwnProperty("1"); } afterTime = getTimer(); log("\tc.hasOwnProperty(\"1\"): " + (afterTime-beforeTime)); } } } class MyClass { public var foo:String = "bar"; }
The above should be pretty straightforward. It’s basically just doing a lot of each operator on each of the types. There is one quirk though: some of the types will throw an Error
if you try to access a property that doesn’t exist, so we’re forced to use a try/catch
block to keep the test running. We’ll see the effect of that below in the performance results. I got the following on a 2.8 Ghz Intel Xeon W3530 with Windows 7:
Operator | Array | Vector | Object | Dictionary | Instance | |||||
---|---|---|---|---|---|---|---|---|---|---|
Hit | Miss | Hit | Miss | Hit | Miss | Hit | Miss | Hit | Miss | |
In | 89 | 173 | 103 | 146 | 68 | 81 | 83 | 109 | 48 | 91 |
Index | 10 | 51 | 8 | 1597 | 72 | 77 | 78 | 96 | 60 | 1643 |
Dot | n/a | n/a | n/a | n/a | 28 | 38 | 28 | 49 | 6 | n/a |
hasOwnProperty | 79 | 96 | 89 | 91 | 80 | 77 | 92 | 95 | 74 | 70 |
The above table shows some very interesting finds:
- Regardless of operator or type, accessing a property that doesn’t exist is always slower than accessing one that does
- Dot operator on
Object
is the best: 35% slower - Index operator on
Vector
is the worst: 200x slower
- Dot operator on
- Dot is always the fastest operator
- The index operator is faster than the
in
operator in 6 of 10 tests- Average index operator speedup is 4.74x
- Average
in
operator speedup is 4.87x
hasOwnProperty
always performs marginally better than thein
operator when the property doesn’t exist
If you have the choice, use the dot operator. It’s usually considered easier to read and it gives you nice compile-time checks and often IDE assistance. If you can’t use the dot operator and don’t care about the value (and can therefore use the in
operator), come back to this page and refer to the above performance data table because it the performance will vary wildly depending on the type of object you use it on. If you don’t, you might end up writing code that executes 19x slower (index an instance and miss) based solely on your choice of operator.
#1 by Henke37 on October 18th, 2010 ·
Wouldn’t it be a fair comparison to compare in vs hasOwnProperty?
#2 by jackson on October 18th, 2010 ·
I started out with just a comparison between the
in
and index operators, but then added the dot operator. I didn’t think to add any functions to the test until your comment. I’ve updated the article with test code, results, and one observation.hasOwnProperty
adds yet more complexity to the test results as it too isn’t definitively better or worse than any other operator.Thanks very much for the suggestion!