I’ve talked about var args, the arguments keyword, and even the length of a function that has default arguments, but I’ve never written an article all about default arguments… until today.

As a refresher, default arguments look like this:

function foo(a:int, b:int=3): void
{
}

In the above example, a is a required argument and b is a default argument since it has a default value. You can call foo with either one or two arguments. If you pass one argument, the value of b will be 3. You can choose any value to be the default so long as it is a compile-time constant, such as a literal or a const variable. Now, let’s take a look at the bytecode from nemo440:

  function private::foo(int,int):void	/* disp_id 0*/
  {
    // local_count=3 max_scope=1 max_stack=1 code_len=3
    0       getlocal0     	
    1       pushscope     	
    2       returnvoid    	
  }

As you can see, nemo440 does not mention anything about either of foo’s arguments having a default or being required. Is it possible that default arguments are purely syntax sugar? Let’s look at some calls to foo:

foo(100, 200);
foo(300);

These generate the following bytecode, again courtesy of nemo440 (and commented by me):

    30      getlocal0     	                 // get "this"
    31      pushbyte      	100              // push 100 as first arg
    33      pushshort     	200              // push 200 as second arg
    36      callpropvoid  	private::foo (2) // call "foo" with two args
    39      getlocal0     	                 // get "this"
    40      pushshort     	300              // push 300 as first arg
    43      callpropvoid  	private::foo (1) // call "foo" with one arg

We see here that foo is called in two ways. The first way involves passing two parameters and the second passing only one, which matches with how we wrote the AS3 code. Since we don’t see bytecode filling in a pushshort 3 for the second parameter of the call using only one parameter, there must be more going on behind the scenes. To find out what that is, let’s look at the far more robust bytecode output courtesy of Apparat:

Trait Method:                
  Name: AbcQName('foo,AbcNamespace(5,'null))                  
  Disp Id: 0                  
  Is Final: false                  
  Is Override: false                  
  Method:                  
	Return Type: AbcQName('void,AbcNamespace(22,'))                    
	Needs Arguments: false                    
	Needs Rest: false                    
	Needs Activation: false                    
	Has Optional Parameters: true                    
	Ignore Rest: false                    
	Is Native: false                    
	DXNS: false                    
	Has Parameter Names: false                    
	Parameters:                    
	  Parameter:                       
		Type: AbcQName('int,AbcNamespace(22,'))                        
		Optional: false                        
	  Parameter:                       
		Type: AbcQName('int,AbcNamespace(22,'))                        
		Optional: true                        
		Default: 3                        
	Method Body:                    
	  Max Stack: 1                      
	  Locals: 3                      
	  InitScopeDepth: 9                      
	  MaxScopeDepth: 10                      
	  Bytecode:                      
		operandStack: 1                        
		scopeStack:   1                        
		localCount:   3                        
		0 exception(s):                        
		3 operation(s):                        
				  +1|-0  GetLocal(0)                          
				  +0|-1  PushScope()                          
				  +0|-0  ReturnVoid()

Apparat gives us access to a treasure trove of information, including the vital “Has Optional Parameters: true” line. Further, it even breaks down the parameters to tell us whether each is required or optional and, if optional, what the default is. Since Apparat doesn’t have access to our AS3 source code, this information must be stored in the SWF file for the Player to use while executing calls to foo.

Now that we know some more about how default parameters work, let’s see if they impose any performance penalty. Since the Player will be filling in default parameters for us at run time depending on our usage of the function, it’s possible that a speed hit could occur if this is slower than simply pushing all of the parameters on the stack. It’s also possible that this could result in a performance boost if the reverse is true. So take a look at this performance test:

package
{
	import flash.display.*;
	import flash.events.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class DefaultParameters extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function log(msg:*): void { __logger.appendText(msg + "\n"); }
 
		public function DefaultParameters()
		{
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			const v:int = 33;
			var beforeTime:int;
			var afterTime:int;
			var requiredTime:int;
			var defaultTime:int;
			var i:int;
			const REPS:int = 100000000;
 
			log("Num Params,Required,Default");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required1(v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default1();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("1," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required2(v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default2();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("2," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required3(v,v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default3();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("3," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required4(v,v,v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default4();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("4," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required5(v,v,v,v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default5();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("5," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required6(v,v,v,v,v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default6();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("6," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required7(v,v,v,v,v,v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default7();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("7," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required8(v,v,v,v,v,v,v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default8();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("8," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required9(v,v,v,v,v,v,v,v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default9();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("9," + requiredTime + "," + defaultTime);
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				required10(v,v,v,v,v,v,v,v,v,v);
			}
			afterTime = getTimer();
			requiredTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				default10();
			}
			afterTime = getTimer();
			defaultTime = afterTime - beforeTime;
 
			log("10," + requiredTime + "," + defaultTime);
		}
 
		private function required1(a:int):void{}
		private function default1(a:int=1):void{}
		private function required2(a:int,b:int):void{}
		private function default2(a:int=1,b:int=2):void{}
		private function required3(a:int,b:int,c:int):void{}
		private function default3(a:int=1,b:int=2,c:int=3):void{}
		private function required4(a:int,b:int,c:int,d:int):void{}
		private function default4(a:int=1,b:int=2,c:int=3,d:int=4):void{}
		private function required5(a:int,b:int,c:int,d:int,e:int):void{}
		private function default5(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5):void{}
		private function required6(a:int,b:int,c:int,d:int,e:int,f:int):void{}
		private function default6(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6):void{}
		private function required7(a:int,b:int,c:int,d:int,e:int,f:int,g:int):void{}
		private function default7(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6,g:int=7):void{}
		private function required8(a:int,b:int,c:int,d:int,e:int,f:int,g:int,h:int):void{}
		private function default8(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6,g:int=7,h:int=8):void{}
		private function required9(a:int,b:int,c:int,d:int,e:int,f:int,g:int,h:int,i:int):void{}
		private function default9(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6,g:int=7,h:int=8,i:int=9):void{}
		private function required10(a:int,b:int,c:int,d:int,e:int,f:int,g:int,h:int,i:int,j:int):void{}
		private function default10(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6,g:int=7,h:int=8,i:int=9,j:int=10):void{}
	}
}

The above test is long and repetitive so that it can test how default parameters affect performance as you add more and more of them. Here are the results I get when I compile with MXMLC 4.1 in release mode and test on a release version of the Flash Player 10.1 plugin on a 2.4 Ghz Intel Core i5 running Mac OS X 10.6:

Num Params Required Default
1 607 639
2 613 654
3 653 688
4 689 732
5 743 792
6 758 898
7 787 1034
8 848 1067
9 878 1101
10 928 1204

And here are the above results in graph form:

Results Graph

The above data and graph shows pretty clearly that there is indeed a performance penalty for using default parameters. Let’s take a look at a graph:

Performance Penalty Graph

We can see that the slowdown in calling time is only about 5% for the first five parameters, but then wildly opens up at the sixth parameter and hovers in the 20-30% range! Thankfully, most functions don’t have anywhere near six default parameters, so this isn’t likely to affect a lot of code. Still, if you do make heavy usage of default parameters (e.g. a 4D Matrix class with defaults for every component), you may want to revisit that code.