Many modern strongly-typed languages have introduced a way for you to not have to type a variable’s type. In C#, you can use var instead of the actual type. In C++, you use auto. AS3 has a similar feature with it’s “untyped” type: *. In those other languages, var and auto are syntax sugar that the compiler replaces with the actual type. Will the AS3 compiler and/or Flash Player do the same for us? Today’s article finds out if it’s safe to skip the type and just use *.

Let’s start with a trivial for loop:

private function typed(): void
{
	const REPS:int = 1000000;
	for (var i:int = 0; i < REPS; ++i)
	{
	}
}

I compiled this using ASC 2.0.0 build 354130 (-debug=false -verbose-stacktraces=false -inline -optimize=true) and then used its swfdump tool to get this bytecode: (my annotations added)

  private function typed():void
  {
    //  derivedName  typed  
    //  method_info  2      
    //  max_stack    2      
    //  max_regs     3      
    //  scope_depth  0      
    //  max_scope    1      
    //  code_length  29     
    bb0
      succs=[bb2]
      0       getlocal0          // get "this"
      1       pushscope          // push "this" to scope
      2       pushbyte        0  // push 0
      3       setlocal1          // set 0 to REPS (unnecessarily...)
      4       pushint            // push 1000000 (not shown for some reason)
      5       setlocal1          // set 1000000 to REPS
      6       pushbyte        0  // push 0
      7       setlocal2          // set 0 to i
      8       pushbyte        0  // push 0
      9       setlocal2          // set 0 to i (redundantly...)
      10      jump       bb2     // go to bb1 block (for loop check)
    bb1
      succs=[bb2]
      11      label              // start of loop body
      12      inclocal_i    2    // ++i
    bb2
      succs=[bb1,bb3]
      13      getlocal2          // get i
      14      pushint            // push 1000000 (instead of REPS...)
      15      iflt       bb1     // if i < 1000000, go to bb1 block (loop body)
    bb3
      succs=[]
      16      returnvoid         // after loop just return void
  }

Now let’s try using the untyped (*) type for the loop iterator i:

private function untyped(): void
{
	const REPS:int = 1000000;
	for (var i:* = 0; i < REPS; ++i)
	{
	}
}

And here’s its bytecode: (also annotated by me)

  private function untyped():void
  {
    //  derivedName  untyped  
    //  method_info  3        
    //  max_stack    2        
    //  max_regs     3        
    //  scope_depth  0        
    //  max_scope    1        
    //  code_length  30       
    bb0
      succs=[bb2]
      0       getlocal0              // get "this"
      1       pushscope              // push "this" to scope
      2       pushbyte            0  // push 0
      3       setlocal1              // set 0 to REPS (unnecessarily...)
      4       pushint                // push 1000000 (not shown for some reason)
      5       setlocal1              // set 1000000 to REPS
      6       pushundefined          // push undefined (unnecessarily...)
      7       setlocal2              // set undefined to i
      8       pushbyte            0  // push 0
      9       setlocal2              // set 0 to i
      10      jump           bb2     // go to bb1 block (for loop check)
    bb1
      succs=[bb2]
      11      label                  // start of loop body
      12      getlocal2              // get i
      13      increment              // increment i
      14      coerce_a               // coerce incremented value to an "atom"
      15      setlocal2              // set incremented, atomized value to i
    bb2
      succs=[bb1,bb3]
      16      getlocal2              // get i
      17      pushint                // push 1000000 (not shown for some reason...)
      18      iflt       bb1         // if i < 1000000, go to bb1 block (loop body)
    bb3
      succs=[]
      19      returnvoid             // after loop just return void
  }

For a three-character change, quite a lot changes have occurred in the bytecode. Setting i to undefined at first isn’t a very big deal since it’s immediately overwritten by the 0 value it is initialized to, but the process of incrementing it is quite different. Here’s the int version:

      12      inclocal_i    2    // ++i

What could be faster than a single, specialized instruction? Probably not the untyped version:

      12      getlocal2              // get i
      13      increment              // increment i
      14      coerce_a               // coerce incremented value to an "atom"
      15      setlocal2              // set incremented, atomized value to i

That’s four times the instructions and none of them are specialized to integers.

Clearly, the compiler is not analyzing the code to figure out that i is really just an int, even though that’s how it’s initialized and that’s how this very simple function uses it.

So, what kind of performance damage is this causing? The following performance test is designed to find out.

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class VariableTyping extends Sprite
	{
		private var logger:TextField = new TextField();
		private function row(...cols): void
		{
			logger.appendText(cols.join(",") + "\n");
		}
 
		public function VariableTyping()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			var beforeTime:int;
			var afterTime:int;
 
			row("Operation", "Time");
 
			beforeTime = getTimer();
			typed();
			afterTime = getTimer();
			row("int", (afterTime-beforeTime));
 
			beforeTime = getTimer();
			untyped();
			afterTime = getTimer();
			row("Untyped", (afterTime-beforeTime));
		}
 
		private function typed(): void
		{
			const REPS:int = 1000000;
			for (var i:int = 0; i < REPS; ++i)
			{
			}
		}
 
		private function untyped(): void
		{
			const REPS:int = 1000000;
			for (var i:* = 0; i < REPS; ++i)
			{
			}
		}
	}
}

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:

Operation Time
int 2
Untyped 20

Variable Typing Performance Graph

Plain and simple, that’s a 10x slowdown caused only by omitting a type. Those extra, generalized increment instructions are way slower than the one specialized instruction the compiler generates for you when you assign the variable a type.

In conclusion, AS3 has a feature that works like C#’s var and C++’s auto but the performance sacrifice it asks of you is huge. It’s best avoided in any performance-critical code.

Spot a bug? Have a question or suggestion? Post a comment!