As a followup to the previous article about object creation and a comment about an alternate object creation strategy, today’s article will expand the coverage of object creation. I will also discuss the performance (and generated bytecode) for creating non-empty objects to see if there are any redeeming factors to the “curly braces” (o = {}) approach.

As the comment said, you can create an empty Object by using the global Object() cast function to cast null to an Object: o = Object(null);. While obscure, this is a valid approach. Let’s look at a test function and the bytecode generated for it by MXMLC 4:

public function objectCastEmpty(): void
{
	var o:Object = Object(null);
}
  function objectCastEmpty():void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=12
    0       getlocal0     	
    1       pushscope     	
    2       findpropstrict	Object
    4       pushnull      	
    5       callproperty  	Object (1)
    8       coerce        	Object
    10      setlocal1     	
    11      returnvoid    	
  }

This approach begins with findpropstrict like the new operator approach but then uses pushnull and callproperty Object(1) to call the global cast function. We’ll see later in this article if that’s actually faster than the new operator’s constructprop approach. For now, let’s look at some functions for creating a new Object with properties starting with the “curly braces” approach:

public function curlyBraces5(): void
{
	var o:Object = {a:100,b:200,c:300,d:400,e:500};
}
  function curlyBraces5():void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=10 code_len=32
    0       getlocal0     	
    1       pushscope     	
    2       pushstring    	"a"
    4       pushbyte      	100
    6       pushstring    	"b"
    8       pushshort     	200
    11      pushstring    	"c"
    13      pushshort     	300
    16      pushstring    	"d"
    18      pushshort     	400
    21      pushstring    	"e"
    23      pushshort     	500
    26      newobject     	{5}
    28      coerce        	Object
    30      setlocal1     	
    31      returnvoid    	
  }

Here we see newobject leveraged by passing the five properties to it. This is very terse, but we’ll see how it performs later on. For now, let’s look at the new operator approach:

public function newOperator5assign(): void
{
	var o:Object = new Object();
	o.a = 100;
	o.b = 200;
	o.c = 300;
	o.d = 400;
	o.e = 500;
}
  function newOperator5assign():void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=40
    0       getlocal0     	
    1       pushscope     	
    2       findpropstrict	Object
    4       constructprop 	Object (0)
    7       coerce        	Object
    9       setlocal1     	
    10      getlocal1     	
    11      pushbyte      	100
    13      setproperty   	a
    15      getlocal1     	
    16      pushshort     	200
    19      setproperty   	b
    21      getlocal1     	
    22      pushshort     	300
    25      setproperty   	c
    27      getlocal1     	
    28      pushshort     	400
    31      setproperty   	d
    33      getlocal1     	
    34      pushshort     	500
    37      setproperty   	e
    39      returnvoid    	
  }

This approach is longer as it is forced to use getlocal1 over and over to set up the assignment via pushshort and setproperty instructions. How about if we used a with block?

public function newOperator5with(): void
{
	var o:Object = new Object();
	with (o)
	{
		a = 100;
		b = 200;
		c = 300;
		d = 400;
		e = 500;
	}
}
  function newOperator5with():void	/* disp_id 0*/
  {
    activation {
      var o:Object	/* slot_id 1 */
    }
    // local_count=3 max_scope=3 max_stack=2 code_len=62
    0       getlocal0     	
    1       pushscope     	
    2       newactivation 	
    3       dup           	
    4       setlocal1     	
    5       pushscope     	
    6       getscopeobject	1
    8       findpropstrict	Object
    10      constructprop 	Object (0)
    13      coerce        	Object
    15      setslot       	1
    17      getscopeobject	1
    19      getslot       	1
    21      dup           	
    22      setlocal2     	
    23      pushwith      	
    24      findproperty  	a
    26      pushbyte      	100
    28      setproperty   	a
    30      findproperty  	b
    32      pushshort     	200
    35      setproperty   	b
    37      findproperty  	c
    39      pushshort     	300
    42      setproperty   	c
    44      findproperty  	d
    46      pushshort     	400
    49      setproperty   	d
    51      findproperty  	e
    53      pushshort     	500
    56      setproperty   	e
    58      popscope      	
    59      kill          	2
    61      returnvoid    	
  }

This version is highly complex compared to the straightforward bytecode we’ve seen so far. We have newactivation, setslot, getslot, getscopeobject, findproperty, and pushwith in addition to the assignment version’s pushshort/setproperty pairs. Since we’ve seen the relevant “assign” and “with” approaches to filling in the empty objects created by the new operator and Object cast approaches, I’ll omit the AS3 and bytecode examples for those and move straight on to the performance test:

package
{
	import flash.display.*;
	import flash.text.*;
	import flash.utils.*;
 
	/**
	*   An app to test the speed of different ways to create an object
	*   @author Jackson Dunstan (jacksondunstan.com)
	*/
	public class ObjectCreation extends Sprite
	{
		public function ObjectCreation()
		{
			var logger:TextField = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
			function log(msg:*): void { logger.appendText(msg+"\n"); }
 
			var i:int;
			var beforeTime:int;
			var afterTime:int;
			var o:Object;
			const REPS:int = 100000;
 
			log("empty");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				o = {};
			}
			afterTime = getTimer();
			log("\tCurly braces: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				o = new Object();
			}
			afterTime = getTimer();
			log("\tNew operator: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				o = Object(null);
			}
			afterTime = getTimer();
			log("\tObject cast: " + (afterTime-beforeTime));
 
			log("5 properties");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				o = {a:100,b:200,c:300,d:400,e:500};
			}
			afterTime = getTimer();
			log("\tCurly braces: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				o = new Object();
				o.a = 100;
				o.b = 200;
				o.c = 300;
				o.d = 400;
				o.e = 500;
			}
			afterTime = getTimer();
			log("\tNew operator (assign): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				o = Object(null);
				o.a = 100;
				o.b = 200;
				o.c = 300;
				o.d = 400;
				o.e = 500;
			}
			afterTime = getTimer();
			log("\tObject cast (assign): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				o = new Object();
				with (o)
				{
					a = 100;
					b = 200;
					c = 300;
					d = 400;
					e = 500;
				}
			}
			afterTime = getTimer();
			log("\tNew operator (with): " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				o = Object(null);
				with (o)
				{
					a = 100;
					b = 200;
					c = 300;
					d = 400;
					e = 500;
				}
			}
			afterTime = getTimer();
			log("\tObject cast (with): " + (afterTime-beforeTime));
		}
	}
}

Note that the number of iterations has changed since the last version of this test. The results are:

Environment Empty 5 Properties
Curly Braces New Operator Object Cast Curly Braces New Operator (assign) Object Cast (assign) New Operator (with) Object Cast (with)
3.0 Ghz Intel Core 2 Duo, Windows XP 19 11 14 48 69 78 423 424
2.0 Ghz Intel Core 2 Duo, Mac OS X 32 22 24 67 115 116 641 640

The results for empty objects are relatively the same as with the test from last time, just with lower numbers due to fewer iterations. While the “curly braces” approach is slowest to create an empty object, it’s fastest when creating an object with five properties. This “curly braces” approach likely overtakes the other techniques after 3-4 properties have been added and then widens its gap as you add more and more. Lastly, the “object cast” approach is roughly the same as the “new operator” approach but somewhat slower with empty object creation.

The conclusion at the end of the last article was to prefer the “new operator” approach over the “curly braces” approach when creating empty objects. This still holds, but reverse is true if you plan on adding a few properties. The “object cast” approach, however, holds no advantages in either scenario.