Dynamic Field Access
AS3 has an interesting feature that is sometimes used to great effect: dynamic classes. These classes can have fields added and removed to them and used like any other field. You can even make your own dynamic classes with the dynamic
keyword. However, all of this fancy functionality comes at a steep performance cost. How steep? Read on to see just how steep.
Firstly, it’s good to know just which classes are dynamic and which aren’t. All classes you write in AS3 are not dynamic unless you explicitly use the dynamic
keyword. This is true even for classes you write that extend classes that are dynamic. For example, if you extend MovieClip
, a dynamic class, and don’t use the dynamic
keyword, your class will not be dynamic.
As for the classes in Flash’s AS3 API, quite a few are dynamic. At the top of any of Adobe’s documentation pages on the Flash API, you’ll see this text:
Class public dynamic class X
If “dynamic” is there, the X
class is dynamic. Here is a partial list of commonly-used dynamic classes:
Dictionary
Array
Class
Object
RegExp
MovieClip
Error
(and many derivatives)
To test the performance of dynamic access, I’ve written a small app. It tests the read and write performance of a built-in non-static class (Point
) and an AS3 non-static class (MyPoint
) against an AS3 dynamic class (MyDynamic
) and several built-in static classes (Dictionary
, Array
, Class
, Object
). As a special case, the I test the performance of a declared x
variable in MyDynamic
against an undeclared z
variable to see if there is any difference.
package { import flash.display.*; import flash.events.*; import flash.utils.*; import flash.text.*; import flash.geom.*; public class DynamicAccess extends Sprite { public static var VAL:Number = 0; private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } public function DynamicAccess() { __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); var i:int; const REPS:int = 10000000; var beforeTime:int; var afterTime:int; var readTime:int; var point:Point = new Point(0,0); var myPoint:MyPoint = new MyPoint(); var myDynamic:MyDynamic = new MyDynamic(); var dictionary:Dictionary = new Dictionary(); dictionary.x = 0; var array:Array = [0]; array.x = 0; var clas:Class = DynamicAccess; var object:Object = {x:0}; log("Type,Read,Write"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { point.x; } afterTime = getTimer(); readTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { point.x = 0; } afterTime = getTimer(); readTime = afterTime - beforeTime; log("Point," + readTime + "," + (afterTime - beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { myPoint.x; } afterTime = getTimer(); readTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { myPoint.x = 0; } afterTime = getTimer(); readTime = afterTime - beforeTime; log("MyPoint," + readTime + "," + (afterTime - beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { myDynamic.x; } afterTime = getTimer(); readTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { myDynamic.x = 0; } afterTime = getTimer(); readTime = afterTime - beforeTime; log("MyDynamic (existing)," + readTime + "," + (afterTime - beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { myDynamic.z; } afterTime = getTimer(); readTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { myDynamic.z = 0; } afterTime = getTimer(); readTime = afterTime - beforeTime; log("MyDynamic (added)," + readTime + "," + (afterTime - beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { dictionary.x; } afterTime = getTimer(); readTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { dictionary.x = 0; } afterTime = getTimer(); readTime = afterTime - beforeTime; log("Dictionary," + readTime + "," + (afterTime - beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { array.x; } afterTime = getTimer(); readTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { array.x = 0; } afterTime = getTimer(); readTime = afterTime - beforeTime; log("Array," + readTime + "," + (afterTime - beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { clas.VAL; } afterTime = getTimer(); readTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { clas.VAL = 0 } afterTime = getTimer(); readTime = afterTime - beforeTime; log("Class," + readTime + "," + (afterTime - beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { object.x; } afterTime = getTimer(); readTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { object.x = 0; } afterTime = getTimer(); readTime = afterTime - beforeTime; log("Object," + readTime + "," + (afterTime - beforeTime)); } 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; }
Here is the test environment I ran the above 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.152.26
- 2.4 Ghz Intel Core i5
- Mac OS X 10.6.6
And here are the results I got:
NOTE: These read times are actually copies of the write times. See the updated performance article with more tests to boot.
Type | Read | Write |
---|---|---|
Point | 29 | 29 |
MyPoint | 27 | 27 |
MyDynamic (existing) | 27 | 27 |
MyDynamic (added) | 402 | 402 |
Dictionary | 423 | 423 |
Array | 514 | 514 |
Class | 114 | 114 |
Object | 407 | 407 |
Here are some conclusions abut these results:
- The non-dynamic access times are, as expected, vastly faster than the dynamic access times. On average, dynamic field access is about 13x slower than non-dynamic field access.
- Accessing the declared fields of a dynamic class is just as fast as accessing the fields of a non-dynamic class
Class
is about 4x faster than other dynamic classes, but still about 3x slower than non-dynamic classes- Read and write are just as fast as each other, regardless of the class’ dynamic status
In general, you should beware the dynamic
keyword and the dynamic classes in the AS3 Flash API when writing performance-critical code. One notable exception is when accessing declared fields of dynamic classes: there’s no slowdown there.
#1 by Henke37 on March 21st, 2011 ·
I was sorta expecting a comparison of property style and array index style accessing of the properties. Perhaps it’s a good idea for a follow up?
#2 by jackson on March 21st, 2011 ·
That’s another way to do dynamic field access. Really, an even more dynamic way as you can build the string you “index” into the property with at run-time. Perhaps I will do that followup…
#3 by lilreaper13 on March 21st, 2011 ·
Strange, the AS3 documentation doesn’t say that TextField is dynamic
#4 by jackson on March 21st, 2011 ·
Sorry, it’s not. I’ve removed it from the post. Good catch!
#5 by rackdoll on March 22nd, 2011 ·
Vector3D has same results as Point class ?
#6 by jackson on March 22nd, 2011 ·
I haven’t tested it, but I would assume so. It’s a static built-in class just like
Point
.#7 by kukulski on March 22nd, 2011 ·
A related case is the use of ‘Object’ to access common properties of unrelated classes. I’ve added it to your test and I see the performance (157 for both read and write) comes in just behind that of Class (147 for both read and write)
#8 by jackson on March 22nd, 2011 ·
Thanks for the addition!
#9 by David Salzer on April 2nd, 2011 ·
You tested Class vs. Object and the result amazed me.
After thinking – the result might not be accurate since you compare read/write of a dynamic property (object.x) with a static non-dynamic property (clas.VAL).
It might be more effective to also test the Class dynamic ability vs. the Object dynamic ability:
#10 by jackson on April 3rd, 2011 ·
This is a very good point! I had not tested access via a
Class
object when the class is dynamic. To test, I added a new static field toMyDynamic
:Then added another test case:
Unfortunately, the test results I get are exactly the same as the test that accesses static variables via a
Class
object representing a non-dynamic class.Thanks for the idea.