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);
				}
			}
		}
	}
}

Run the test

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

Making describeTypeJSON Faster

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!