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 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 the in 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.