Making describeTypeJSON 50x Faster than describeType
The hidden describeTypeJSON
function is faster than the XML-based describeType
function by default, but we can make it even faster. Today’s article describe just how this is done and achieves a nearly 10x speedup!
Unlike describeType
, describeTypeJSON
lets us specify what parts of the class we’d like described. If we’re only interested in, say, the class’ variables then there’s no need to spend time describing its methods. We’re allowed to specify exactly what we want via a series of “flags” which we can use a bitwise OR (|
) operator to combine and order multiple parts of the class to be described.
Here are the available flags:
- INCLUDE_BASES
- INCLUDE_INTERFACES
- INCLUDE_VARIABLES
- INCLUDE_ACCESSORS
- INCLUDE_METHODS
- INCLUDE_METADATA
- INCLUDE_CONSTRUCTOR
- INCLUDE_TRAITS
- INCLUDE_ITRAITS
- HIDE_OBJECT
Unfortunately, the SwiftSuspenders class doesn’t provide raw access to these flags, only collections of them that include everything for instances and classes. To work around this, I’ve modified their class to provide such raw access:
/* * Copyright (c) 2011 the original author or authors * * Permission is hereby granted to use, modify, and distribute this file * in accordance with the terms of the license agreement accompanying it. */ package avmplus { /** * Makes the hidden, inofficial function describeTypeJSON available outside of the avmplus * package. * * <strong>As Adobe doen't officially support this method and it is only visible to client * code by accident, it should only ever be used with runtime-detection and automatic fallback * on describeType.</strong> * * @see http://www.tillschneidereit.de/2009/11/22/improved-reflection-support-in-flash-player-10-1/ */ public class DescribeTypeJSON { //---------------------- Public Properties ----------------------// public static var available : Boolean = describeTypeJSON != null; public static const INSTANCE_FLAGS:uint = INCLUDE_BASES | INCLUDE_INTERFACES | INCLUDE_VARIABLES | INCLUDE_ACCESSORS | INCLUDE_METHODS | INCLUDE_METADATA | INCLUDE_CONSTRUCTOR | INCLUDE_TRAITS | USE_ITRAITS | HIDE_OBJECT; public static const CLASS_FLAGS:uint = INCLUDE_INTERFACES | INCLUDE_VARIABLES | INCLUDE_ACCESSORS | INCLUDE_METHODS | INCLUDE_METADATA | INCLUDE_TRAITS | HIDE_OBJECT; public static const INCLUDE_BASES:uint = avmplus.INCLUDE_BASES; public static const INCLUDE_INTERFACES:uint = avmplus.INCLUDE_INTERFACES; public static const INCLUDE_VARIABLES:uint = avmplus.INCLUDE_VARIABLES; public static const INCLUDE_ACCESSORS:uint = avmplus.INCLUDE_ACCESSORS; public static const INCLUDE_METHODS:uint = avmplus.INCLUDE_METHODS; public static const INCLUDE_METADATA:uint = avmplus.INCLUDE_METADATA; public static const INCLUDE_CONSTRUCTOR:uint = avmplus.INCLUDE_CONSTRUCTOR; public static const INCLUDE_TRAITS:uint = avmplus.INCLUDE_TRAITS; public static const INCLUDE_ITRAITS:uint = avmplus.USE_ITRAITS; public static const HIDE_OBJECT:uint = avmplus.HIDE_OBJECT; //---------------------- Public Methods ----------------------// public function DescribeTypeJSON() { } public function describeType(target : Object, flags : uint) : Object { return describeTypeJSON(target, flags); } public function getInstanceDescription(type : Class) : Object { return describeTypeJSON(type, INSTANCE_FLAGS); } public function getClassDescription(type : Class) : Object { return describeTypeJSON(type, CLASS_FLAGS); } } }
To test these out I’ve created a simple Person
class that extends PersonBase
and implements IPerson
, all for the purposes of testing. I’ve added some static variables and methods, too. Here’s how those files look:
///////////// // IPerson.as ///////////// package { public interface IPerson { function getName(): String; } } //////////////// // PersonBase.as //////////////// package { public class PersonBase { public var id:int; } } //////////// // Person.as //////////// package { [Version(major="1",minor="2")] public class Person extends PersonBase implements IPerson { public static var numPeople:int; public var name:String; public var age:int; public var alive:Boolean; public function getName(): String { return name; } public function birthday(): void { age++; } public static function birth(): void { numPeople++; } } }
I then tried out each individual flag—including the “0” flag, meaning nothing—with the following results:
0: name = Person isFinal = true traits = null isStatic = true isDynamic = true INCLUDE_BASES: name = Person isFinal = true traits = null isStatic = true isDynamic = true INCLUDE_INTERFACES: name = Person isFinal = true traits = null isStatic = true isDynamic = true INCLUDE_VARIABLES: name = Person isFinal = true traits = null isStatic = true isDynamic = true INCLUDE_ACCESSORS: name = Person isFinal = true traits = null isStatic = true isDynamic = true INCLUDE_METHODS: name = Person isFinal = true traits = null isStatic = true isDynamic = true INCLUDE_METADATA: name = Person isFinal = true traits = null isStatic = true isDynamic = true INCLUDE_CONSTRUCTOR: name = Person isFinal = true traits = null isStatic = true isDynamic = true INCLUDE_TRAITS: name = Person isFinal = true traits = [object Object] methods = null bases = null accessors = null variables = null interfaces = null constructor = null metadata = null isStatic = true isDynamic = true INCLUDE_ITRAITS: name = Person isFinal = false traits = null isStatic = false isDynamic = false HIDE_OBJECT: name = Person isFinal = true traits = null isStatic = true isDynamic = true
As you can see, only INCLUDE_TRAITS
does anything except describe the basic five fields: name
, isFinal
, traits
, isStatic
, and isDynamic
. We need to use a bitwise OR (|
) of each flag with INCLUDE_TRAITS
to enable each trait. Here’s how those results look:
INCLUDE_TRAITS|INCLUDE_BASES: name = Person isFinal = true traits = [object Object] methods = null bases = Class,Object 0 = Class 1 = Object accessors = null variables = null interfaces = null constructor = null metadata = null isStatic = true isDynamic = true INCLUDE_TRAITS|INCLUDE_INTERFACES: name = Person isFinal = true traits = [object Object] methods = null bases = null accessors = null variables = null interfaces = constructor = null metadata = null isStatic = true isDynamic = true INCLUDE_TRAITS|INCLUDE_VARIABLES: name = Person isFinal = true traits = [object Object] methods = null bases = null accessors = null variables = [object Object] 0 = [object Object] type = int name = numPeople access = readwrite uri = null metadata = null interfaces = null constructor = null metadata = null isStatic = true isDynamic = true INCLUDE_TRAITS|INCLUDE_ACCESSORS: name = Person isFinal = true traits = [object Object] methods = null bases = null accessors = [object Object] 0 = [object Object] type = * uri = null name = prototype access = readonly declaredBy = Class metadata = null variables = null interfaces = null constructor = null metadata = null isStatic = true isDynamic = true INCLUDE_TRAITS|INCLUDE_METHODS: name = Person isFinal = true traits = [object Object] methods = [object Object],[object Object],[object Object],[object Object] 0 = [object Object] name = hasOwnProperty uri = http://adobe.com/AS3/2006/builtin returnType = Boolean parameters = [object Object] 0 = [object Object] type = * optional = true declaredBy = Object metadata = null 1 = [object Object] name = propertyIsEnumerable uri = http://adobe.com/AS3/2006/builtin returnType = Boolean parameters = [object Object] 0 = [object Object] type = * optional = true declaredBy = Object metadata = null 2 = [object Object] name = isPrototypeOf uri = http://adobe.com/AS3/2006/builtin returnType = Boolean parameters = [object Object] 0 = [object Object] type = * optional = true declaredBy = Object metadata = null 3 = [object Object] name = birth uri = null returnType = void parameters = declaredBy = Person metadata = null bases = null accessors = null variables = null interfaces = null constructor = null metadata = null isStatic = true isDynamic = true INCLUDE_TRAITS|INCLUDE_METADATA: name = Person isFinal = true traits = [object Object] methods = null bases = null accessors = null variables = null interfaces = null constructor = null metadata = isStatic = true isDynamic = true INCLUDE_TRAITS|INCLUDE_CONSTRUCTOR: name = Person isFinal = true traits = [object Object] methods = null bases = null accessors = null variables = null interfaces = null constructor = null metadata = null isStatic = true isDynamic = true INCLUDE_TRAITS|INCLUDE_ITRAITS: name = Person isFinal = false traits = [object Object] methods = null bases = null accessors = null variables = null interfaces = null constructor = null metadata = null isStatic = false isDynamic = false INCLUDE_TRAITS|HIDE_OBJECT: name = Person isFinal = true traits = [object Object] methods = null bases = null accessors = null variables = null interfaces = null constructor = null metadata = null isStatic = true isDynamic = true
More or less, they look the way you’d expect. The flags allow you to describe individual elements of the class without describing any elements you’re not interested in.
Now let’s test the performance of describing each element, none of the elements, and all of the elements. Here’s the performance test:
package { import flash.display.*; import flash.utils.*; import flash.text.*; import avmplus.*; public class DescribeTypeJSONSpeed extends Sprite { private var logger:TextField = new TextField(); private function row(...cols): void { logger.appendText(cols.join(",") + "\n"); } public function DescribeTypeJSONSpeed() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; logger.width = stage.stageWidth; logger.height = stage.stageHeight; addChild(logger); printExamples(); row(); init(); } private function printExamples(): void { var describer:DescribeTypeJSON = new DescribeTypeJSON(); row("0:"); printObject(describer.describeType(Person, 0)); row("INCLUDE_BASES:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_BASES)); row("INCLUDE_INTERFACES:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_INTERFACES)); row("INCLUDE_VARIABLES:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_VARIABLES)); row("INCLUDE_ACCESSORS:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_ACCESSORS)); row("INCLUDE_METHODS:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_METHODS)); row("INCLUDE_METADATA:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_METADATA)); row("INCLUDE_CONSTRUCTOR:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_CONSTRUCTOR)); row("INCLUDE_TRAITS:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS)); row("INCLUDE_ITRAITS:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_ITRAITS)); row("HIDE_OBJECT:"); printObject(describer.describeType(Person, DescribeTypeJSON.HIDE_OBJECT)); row("INCLUDE_TRAITS|INCLUDE_BASES:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_BASES)); row("INCLUDE_TRAITS|INCLUDE_INTERFACES:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_INTERFACES)); row("INCLUDE_TRAITS|INCLUDE_VARIABLES:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_VARIABLES)); row("INCLUDE_TRAITS|INCLUDE_ACCESSORS:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_ACCESSORS)); row("INCLUDE_TRAITS|INCLUDE_METHODS:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_METHODS)); row("INCLUDE_TRAITS|INCLUDE_METADATA:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_METADATA)); row("INCLUDE_TRAITS|INCLUDE_CONSTRUCTOR:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_CONSTRUCTOR)); row("INCLUDE_TRAITS|INCLUDE_ITRAITS:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_ITRAITS)); row("INCLUDE_TRAITS|HIDE_OBJECT:"); printObject(describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.HIDE_OBJECT)); } private function init(): void { const REPS:int = 100000; var i:int; var beforeTime:int; var afterTime:int; var describer:DescribeTypeJSON = new DescribeTypeJSON(); row("Flags", "Time"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, 0); } afterTime = getTimer(); row("0", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_BASES); } afterTime = getTimer(); row("INCLUDE_TRAITS|INCLUDE_BASES", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_INTERFACES); } afterTime = getTimer(); row("INCLUDE_TRAITS|INCLUDE_INTERFACES", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_VARIABLES); } afterTime = getTimer(); row("INCLUDE_TRAITS|INCLUDE_VARIABLES", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_ACCESSORS); } afterTime = getTimer(); row("INCLUDE_TRAITS|INCLUDE_ACCESSORS", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_METHODS); } afterTime = getTimer(); row("INCLUDE_TRAITS|INCLUDE_METHODS", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_METADATA); } afterTime = getTimer(); row("INCLUDE_TRAITS|INCLUDE_METADATA", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_CONSTRUCTOR); } afterTime = getTimer(); row("INCLUDE_TRAITS|INCLUDE_CONSTRUCTOR", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.INCLUDE_ITRAITS); } afterTime = getTimer(); row("INCLUDE_TRAITS|INCLUDE_ITRAITS", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS|DescribeTypeJSON.HIDE_OBJECT); } afterTime = getTimer(); row("INCLUDE_TRAITS|HIDE_OBJECT", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { describer.describeType(Person, DescribeTypeJSON.CLASS_FLAGS); } afterTime = getTimer(); row("CLASS_FLAGS", (afterTime-beforeTime)); } private 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]; row(tabs + k + " = " + v); if (v) { printObject(v, numTabs+1); } } } } }
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:
Flags | Time |
---|---|
0 | 129 |
INCLUDE_TRAITS|INCLUDE_BASES | 312 |
INCLUDE_TRAITS|INCLUDE_INTERFACES | 307 |
INCLUDE_TRAITS|INCLUDE_VARIABLES | 546 |
INCLUDE_TRAITS|INCLUDE_ACCESSORS | 566 |
INCLUDE_TRAITS|INCLUDE_METHODS | 974 |
INCLUDE_TRAITS|INCLUDE_METADATA | 303 |
INCLUDE_TRAITS|INCLUDE_CONSTRUCTOR | 287 |
INCLUDE_TRAITS|INCLUDE_ITRAITS | 288 |
INCLUDE_TRAITS|HIDE_OBJECT | 286 |
CLASS_FLAGS | 1069 |
There’s a very wide range of performance depending on the flags you pass to describeTypeJSON
. Describing everything, as expected, takes the longest of all and, also expected, describing just the basics takes the least amount of time. The range between these is huge though! It takes nearly 10x longer to describe everything than just the basics. In between there is also a wide range of results. Describing most things takes just twice as long as “the basics” but describing variables and accessors takes about 4x longer than “the basics”. Worst of all is describing methods. This takes nearly as long as describing everything, so you’re barely saving anything with this option.
The real optimizations with describeTypeJSON
come when you can cut down your request for descriptions to just what you need and when you don’t need the class’ methods described. You can probably achieve a 5x speedup compared to describing everything and, in some cases, a 10x when you just need the basics. Remember that this is on top of the 5x speedup you’re already getting by switching away from the XML-based describeType
and you’re looking at a 25-50x overall speedup!
Spot a bug? Have a question or suggestion? Post a comment!
#1 by demonsu on November 7th, 2015 ·
try this change , u might got a better result:
for (i = 0; i < REPS; ++i)
{
describer.describeType(Person, DescribeTypeJSON.INCLUDE_TRAITS | DescribeTypeJSON.INCLUDE_BASES);
}
to
var type:int= DescribeTypeJSON.INCLUDE_TRAITS | DescribeTypeJSON.INCLUDE_BASES;
for (i = 0; i < REPS; ++i)
{
describer.describeType(Person, type);
}