describeType vs. describeTypeJSON
flash.utils.describeType
has been around since Flash 9 and is the standard way to find out interesting information about a Class
type, including its metadata/annotations. However, there’s a hidden function called describeTypeJSON
that provides an interesting alternative. Since describeType
is notoriously slow, could describeTypeJSON
be the speedy alternative we’ve been looking for? Today’s article puts them to the test!
First of all, you should read the excellent article by Till Schneidereit about describeTypeJSON
to find out lots about its internals. Suffice to say that it’s a hidden function in the avmplus
package with no public
visibility. We therefore need to create a wrapper class in the avmplus
package so we can get at it. Thankfully, the SwiftSuspeners project has provided just such a class.
So what do you get out of describeTypeJSON
? Well, just like when you use the JSON
class to parse a String
, you’ll get an untyped Object
. As an example, I tested this simple class:
package { public class Person { public var name:String; public var age:int; public var alive:Boolean; } }
And printed the Object
that describeTypeJSON
returned with a simple recursive function:
function printObject(obj:Object, numTabs:int=0): void { var tabs:String = ""; for (var i:int = 0; i < numTabs; ++i) { tabs += "\t"; } for (var k:* in obj) { var v:* = obj[k]; trace(tabs + k + " = " + v); if (v) { printObject(v, numTabs+1); } } }
Here’s what I got:
isDynamic = false isStatic = false name = Person traits = [object Object] interfaces = methods = null accessors = null bases = Object 0 = Object metadata = [object Object] 0 = [object Object] value = [object Object] 0 = [object Object] value = 24 key = pos name = __go_to_definition_help constructor = null variables = [object Object],[object Object],[object Object] 0 = [object Object] uri = null name = alive access = readwrite metadata = [object Object] 0 = [object Object] value = [object Object] 0 = [object Object] value = 95 key = pos name = __go_to_definition_help type = Boolean 1 = [object Object] uri = null name = name access = readwrite metadata = [object Object] 0 = [object Object] value = [object Object] 0 = [object Object] value = 47 key = pos name = __go_to_definition_help type = String 2 = [object Object] uri = null name = age access = readwrite metadata = [object Object] 0 = [object Object] value = [object Object] 0 = [object Object] value = 73 key = pos name = __go_to_definition_help type = int isFinal = false
The formatting isn’t great, but you can clearly see that the output is very similar to what we get from good old describeType
:
<type name="Person" base="Class" isDynamic="true" isFinal="true" isStatic="true"> <extendsClass type="Class"/> <extendsClass type="Object"/> <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/> <factory type="Person"> <extendsClass type="Object"/> <variable name="alive" type="Boolean"> <metadata name="__go_to_definition_help"> <arg key="pos" value="95"/> </metadata> </variable> <variable name="name" type="String"> <metadata name="__go_to_definition_help"> <arg key="pos" value="47"/> </metadata> </variable> <variable name="age" type="int"> <metadata name="__go_to_definition_help"> <arg key="pos" value="73"/> </metadata> </variable> <metadata name="__go_to_definition_help"> <arg key="pos" value="24"/> </metadata> </factory> </type>
Now on to the main point of this article: speed. Which is faster? Let’s run each 10,000 times and see. Here’s a little test app that does just that:
package { import flash.display.*; import flash.utils.*; import flash.text.*; import avmplus.*; public class DescribeTypeSpeed extends Sprite { private var logger:TextField = new TextField(); private function row(...cols): void { logger.appendText(cols.join(",") + "\n"); } public function DescribeTypeSpeed() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); init(); } private function init(): void { const REPS:int = 10000; var i:int; var beforeTime:int; var afterTime:int; var describer:DescribeTypeJSON = new DescribeTypeJSON(); row("Operation", "Time"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { flash.utils.describeType(Person); } afterTime = getTimer(); row("describeType", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.getInstanceDescription(Person); } afterTime = getTimer(); row("describeTypeJSON", (afterTime-beforeTime)); } } }
I tested this app using the following environment:
- Release version of Flash Player 14.0.0.125
- 2.3 Ghz Intel Core i7-3615QM
- Mac OS X 10.9.2
- Google Chrome 35.0.1916.153
- ASC 2.0.0 build 354130 (
-debug=false -verbose-stacktraces=false -inline -optimize=true
)
And got these results:
Operation | Time |
---|---|
describeType | 643 |
describeTypeJSON | 135 |
It turns out that describeTypeJSON
is about 5x faster than describeType
. This means it’s probably worth jumping through the hoops necessary to get access to the hidden functionality. The access is still pretty slow (0.135 ms each on this relatively-fast machine), so don’t call the function more than you need to.
One tried-and-true method of optimizing this is to cache the results in a static Dictionary
since the types don’t change at runtime. This makes subsequent calls to either describeType
or describeTypeJSON
almost free. If you do this, the speedup gained from describeTypeJSON
will only apply to the first call to describe a Class
, but even that can be quite significant.
Spot a bug? Have a question or suggestion? Post a comment!
#1 by ben w on June 23rd, 2014 ·
Thought it would be faster, but I would also mention the fact that you can filter the output with flags.
If you were after variable metadata only your might not need all of them…
i.e. you could maybe ignore
avmplus.INCLUDE_CONSTRUCTOR
avmplus.INCLUDE_METHODS
avmplus.INCLUDE_ACCESSORS
if you knew you were only after:
avmplus.INCLUDE_VARIABLES
It could lead to a bigger speed increase as less information is needed, with the traditional describeType all the info is returned regardless how much of it you are interested in.
#2 by jackson on June 23rd, 2014 ·
Very good point! I tried just
avmplus.INCLUDE_VARIABLES
and got about 12-13 ms on the same machine, a 10x speedup. It compares apples to oranges, but sometimes you’re not after everything indescribeType
’s package deal.#3 by Deril on June 24th, 2014 ·
Hi,
I did research on it half year ago.. if you drop metadata stuff you get about *10 increase over describeType.
https://github.com/MindScriptAct/mvcExpress-framework/issues/19
#4 by jackson on June 24th, 2014 ·
Glad to hear your results line up with my findings.
#5 by Alexander on June 26th, 2014 ·
So 135ms / 10000calls will be equal to 0.134ms/1call? ))
#6 by jackson on June 26th, 2014 ·
Yes, in the test environment. But the test CPU is really fast compared to slower chips such as those found in mobile devices. Here’s a random comparison I found of a CPU very much like the one in the test environment to the CPU in the iPad 1 and iPhone 4. The test environment CPU is about 28x faster, which translates to
describeTypeJSON
taking 3.752 ms/call. If you’re trying to get a game to run at 30 FPS, you only get 33.3 ms/frame, so 3.752 ms for adescribeTypeJSON
call takes up over 11% of your total CPU allotment. This means it’s really expensive.Now compare to looking up the cached results of
describeTypeJSON
in aDictionary
. In this article where I used the same CPU I could perform two millionDictionary
lookups in 568 ms for an average of 0.000284 ms/call. Apply the 28x multiplier for iPad 1 or iPhone 4 and you get 0.007952 ms/call on those devices. That’s only 0.024% of your total frame time for 30 FPS, which is quite tolerable.#7 by Alexander on July 1st, 2014 ·
I meant 135ms / 10k = 0.0135ms/call :-)