One of the very nice features of AS3 (and AS2 and JavaScript for that matter) is that you can dynamically access the fields of any object. This leads to much more dynamic code since you no longer need to know what field to access at compile time. As we’ve often seen with other dynamic features, this can come at a steep cost in terms of performance. Today we’ll see just how slow accessing fields this way is to get a good idea of just how much performance we give up when using this feature.

By way of introduction, here are the two ways of accessing fields of objects in question:

function foo(p:Point): void
{
	p.x; // "dot" operator method of reading fields
	p["x"]; // "index" operator method of reading fields
	p.x = 1; // "dot" operator method of writing fields
	p["x"] = 1; // "index" operator method of writing fields
}

In the dot operator method, you must know what field to access at compile time. In contrast, the index operator method basically shuts down all error checking on the compiler and lets you index any object with any expression: a literal, a variable, or the result of a computation.

With that in mind, I’ve updated the performance-testing app from Dynamic Field Access to include index operator reads and writes and do more tests per loop to lessen the impact of loop overhead. Performance results and graphs follow this test code.

package
{
	import flash.display.*;
	import flash.events.*;
	import flash.utils.*;
	import flash.text.*;
	import flash.geom.*;
 
	public class FieldAccessMethods extends Sprite
	{
		public static var VAL:Number=0;
 
		private var __logger:TextField = new TextField();
		private function row(...vals): void
		{
			__logger.appendText(vals.join(",")+"\n");
		}
 
		public function FieldAccessMethods()
		{
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			var i:int;
			const REPS:int = 1000000;
			var beforeTime:int;
			var afterTime:int;
			var readDotTime:int;
			var writeDotTime:int;
			var readIndexTime:int;
			var writeIndexTime:int;
			var p:Point = new Point(0,0);
			var mp:MyPoint = new MyPoint();
			var md:MyDynamic = new MyDynamic();
			var d:Dictionary = new Dictionary();
				d.x=0;
			var a:Array = [0];
				a.x=0;
			var c:Class = DynamicAccess;
			var dc:Class = MyDynamic;
			var o:Object = {x:0};
 
			row("Type", "Read (dot)", "Write (dot)", "Read (index)", "Write (index)");
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				p.x;p.x;p.x;p.x;p.x;
				p.x;p.x;p.x;p.x;p.x;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				p.x=0;p.x=0;p.x=0;p.x=0;p.x=0;
				p.x=0;p.x=0;p.x=0;p.x=0;p.x=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				p["x"];p["x"];p["x"];p["x"];p["x"];
				p["x"];p["x"];p["x"];p["x"];p["x"];
			}
			afterTime = getTimer();
			readIndexTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				p["x"]=0;p["x"]=0;p["x"]=0;p["x"]=0;p["x"]=0;
				p["x"]=0;p["x"]=0;p["x"]=0;p["x"]=0;p["x"]=0;
			}
			afterTime = getTimer();
			writeIndexTime = afterTime - beforeTime;
			row("Point", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				mp.x;mp.x;mp.x;mp.x;mp.x;
				mp.x;mp.x;mp.x;mp.x;mp.x;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				mp.x=0;mp.x=0;mp.x=0;mp.x=0;mp.x=0;
				mp.x=0;mp.x=0;mp.x=0;mp.x=0;mp.x=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				mp["x"];mp["x"];mp["x"];mp["x"];mp["x"];
				mp["x"];mp["x"];mp["x"];mp["x"];mp["x"];
			}
			afterTime = getTimer();
			readIndexTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				mp["x"]=0;mp["x"]=0;mp["x"]=0;mp["x"]=0;mp["x"]=0;
				mp["x"]=0;mp["x"]=0;mp["x"]=0;mp["x"]=0;mp["x"]=0;
			}
			afterTime = getTimer();
			writeIndexTime = afterTime - beforeTime;
			row("MyPoint", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				md.x;md.x;md.x;md.x;md.x;
				md.x;md.x;md.x;md.x;md.x;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				md.x=0;md.x=0;md.x=0;md.x=0;md.x=0;
				md.x=0;md.x=0;md.x=0;md.x=0;md.x=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				md["x"];md["x"];md["x"];md["x"];md["x"];
				md["x"];md["x"];md["x"];md["x"];md["x"];
			}
			afterTime = getTimer();
			readIndexTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				md["x"]=0;md["x"]=0;md["x"]=0;md["x"]=0;md["x"]=0;
				md["x"]=0;md["x"]=0;md["x"]=0;md["x"]=0;md["x"]=0;
			}
			afterTime = getTimer();
			writeIndexTime = afterTime - beforeTime;
			row("MyDynamic (existing)", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				md.z;md.z;md.z;md.z;md.z;
				md.z;md.z;md.z;md.z;md.z;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				md.z=0;md.z=0;md.z=0;md.z=0;md.z=0;
				md.z=0;md.z=0;md.z=0;md.z=0;md.z=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				md["z"];md["z"];md["z"];md["z"];md["z"];
				md["z"];md["z"];md["z"];md["z"];md["z"];
			}
			afterTime = getTimer();
			readIndexTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				md["z"]=0;md["z"]=0;md["z"]=0;md["z"]=0;md["z"]=0;
				md["z"]=0;md["z"]=0;md["z"]=0;md["z"]=0;md["z"]=0;
			}
			afterTime = getTimer();
			writeIndexTime = afterTime - beforeTime;
			row("MyDynamic (added)", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				d.x;d.x;d.x;d.x;d.x;
				d.x;d.x;d.x;d.x;d.x;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				d.x=0;d.x=0;d.x=0;d.x=0;d.x=0;
				d.x=0;d.x=0;d.x=0;d.x=0;d.x=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				d["x"];d["x"];d["x"];d["x"];d["x"];
				d["x"];d["x"];d["x"];d["x"];d["x"];
			}
			afterTime = getTimer();
			readIndexTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				d["x"]=0;d["x"]=0;d["x"]=0;d["x"]=0;d["x"]=0;
				d["x"]=0;d["x"]=0;d["x"]=0;d["x"]=0;d["x"]=0;
			}
			afterTime = getTimer();
			writeIndexTime = afterTime - beforeTime;
			row("Dictionary", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				a.x;a.x;a.x;a.x;a.x;
				a.x;a.x;a.x;a.x;a.x;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				a.x=0;a.x=0;a.x=0;a.x=0;a.x=0;
				a.x=0;a.x=0;a.x=0;a.x=0;a.x=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				a["x"];a["x"];a["x"];a["x"];a["x"];
				a["x"];a["x"];a["x"];a["x"];a["x"];
			}
			afterTime = getTimer();
			readIndexTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				a["x"]=0;a["x"]=0;a["x"]=0;a["x"]=0;a["x"]=0;
				a["x"]=0;a["x"]=0;a["x"]=0;a["x"]=0;a["x"]=0;
			}
			afterTime = getTimer();
			writeIndexTime = afterTime - beforeTime;
			row("Array", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				c.VAL;c.VAL;c.VAL;c.VAL;c.VAL;
				c.VAL;c.VAL;c.VAL;c.VAL;c.VAL;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				c.VAL=0;c.VAL=0;c.VAL=0;c.VAL=0;c.VAL=0;
				c.VAL=0;c.VAL=0;c.VAL=0;c.VAL=0;c.VAL=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				c["VAL"];c["VAL"];c["VAL"];c["VAL"];c["VAL"];
				c["VAL"];c["VAL"];c["VAL"];c["VAL"];c["VAL"];
			}
			afterTime = getTimer();
			readIndexTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;
				c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;
			}
			afterTime = getTimer();
			writeIndexTime = afterTime - beforeTime;
			row("Static Class", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				dc.VAL;dc.VAL;dc.VAL;dc.VAL;dc.VAL;
				dc.VAL;dc.VAL;dc.VAL;dc.VAL;dc.VAL;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				dc.VAL=0;dc.VAL=0;dc.VAL=0;dc.VAL=0;dc.VAL=0;
				dc.VAL=0;dc.VAL=0;dc.VAL=0;dc.VAL=0;dc.VAL=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				dc["VAL"];dc["VAL"];dc["VAL"];dc["VAL"];dc["VAL"];
				dc["VAL"];dc["VAL"];dc["VAL"];dc["VAL"];dc["VAL"];
			}
			afterTime = getTimer();
			readIndexTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;
				dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;
			}
			afterTime = getTimer();
			writeIndexTime = afterTime - beforeTime;
			row("Dynamic Class", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
 
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				o.x;o.x;o.x;o.x;o.x;
				o.x;o.x;o.x;o.x;o.x;
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				o.x=0;o.x=0;o.x=0;o.x=0;o.x=0;
				o.x=0;o.x=0;o.x=0;o.x=0;o.x=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				o["x"];o["x"];o["x"];o["x"];o["x"];
				o["x"];o["x"];o["x"];o["x"];o["x"];
			}
			afterTime = getTimer();
			readDotTime = afterTime - beforeTime;
			beforeTime = getTimer();
			for (i=0; i < REPS; ++i)
			{
				o["x"]=0;o["x"]=0;o["x"]=0;o["x"]=0;o["x"]=0;
				o["x"]=0;o["x"]=0;o["x"]=0;o["x"]=0;o["x"]=0;
			}
			afterTime = getTimer();
			writeDotTime = afterTime - beforeTime;
			row("Object", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
		}
 
		private function foo(): void {}
	}
}
class MyPoint
{
	public var x:Number=0;
	public var y:Number=0;
}
dynamic class MyDynamic
{
	public var x:Number=0;
	public var y:Number=0;
	public static var VAL:Number=0;
}

Here is the test environment I ran the test app on:

  • Flex SDK (MXMLC) 4.1.0.16076, compiling in release mode (no debugging or verbose stack traces)
  • Release version of Flash Player 10.2.154.27
  • 2.4 Ghz Intel Core i5
  • Mac OS X 10.6.7

And here are the results I got:

Type Read (dot) Write (dot) Read (index) Write (index)
Point 2 12 816 892
MyPoint 2 9 812 890
MyDynamic (existing) 3 9 813 892
MyDynamic (added) 555 369 855 1021
Dictionary 305 427 995 1168
Array 404 510 1207 1380
Static Class 143 103 841 898
Dynamic Class 140 103 809 886
Object 831 1040 809 886

Here are those same results in graph form:

Field Access Performance (all)

For all of the types except Object, the index operator is more expensive. Way more expensive. For the dynamic Array and Dictionary types whose access is already slow, the performance hit is “only” about 3x. For the fast-access types (static classes), the performance hit can be up to 400x! Now let’s look at just the dot operator test results:

Field Access Performance (dot operator)

Here we see that accessing static fields is tons faster than accessing dynamic fields. Keep in mind that the “slow” accesses here are already hugely faster than the same classes (except Object) when accessed with the index operator. All this means is that static field access is comparatively very fast.

Now, let’s look at just the index operator test results:

Field Access Performance (index operator)

What’s striking about this graph is how most of the various performance differences between the different object types have disappeared. Dynamic class fields, Dictionary, and Array are slower than everything else. Strangely, this even includes Object, which seems remarkably indifferent to access methods.

As for reading versus writing fields, we see two completely different pictures depending on access method. Writing is always about 10% slower than reading when using the index operator. On the other hand, the dot operator yields inconsistent results. The dynamic Object, Dictionary, and Array classes are consistent with the dynamic index operator in being slower to write. This is even the case for the static fields, except with dynamic class instances and Class objects. Still, the difference seems to be plus or minus 20% at most in all cases.

In sum, do not use the index operator to access fields in performance critical code. An exception would be when dealing with plain Object variables, but these are so slow that you’d best avoid them in the first place.