While I was working on last week’s article it became apparent that something strange was going on with the arguments keyword in AS3. Last week I showed that even after you’ve changed the parameters of a function, you can still get the original values by indexing into arguments. This implies a copy and a copy implies a slowdown. Read on to see if there really is a slowdown and, if there is, what kind of performance impact there is.

Firstly, read over the following simple test app:

package
{
	import flash.text.*;
	import flash.utils.*;
	import flash.display.*;
 
	/**
	*   An app to demonstrate the slowdown of var args
	*   and functions using the "arguments" keyword
	*   @author Jackson Dunstan
	*/
	public class ArgumentsSlowdown extends Sprite
	{
		public function ArgumentsSlowdown()
		{
			var logger:TextField = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
			function log(msg:*): void { logger.appendText(msg + "\n"); }
 
			const ITERATIONS:int = 1000000;
			var i:int;
			var beforeTime:int;
			var afterTime:int;
			var dummyArray:Array = [42];
 
			log("1");
 
			beforeTime = getTimer();
			for (i = 0; i < ITERATIONS; ++i)
			{
				plain1(dummyArray);
			}
			afterTime = getTimer();
			log("\tplain: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < ITERATIONS; ++i)
			{
				varArgs(dummyArray);
			}
			afterTime = getTimer();
			log("\tvar args: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < ITERATIONS; ++i)
			{
				arguments1(dummyArray);
			}
			afterTime = getTimer();
			log("\targuments: " + (afterTime-beforeTime));
 
			log("25");
 
			beforeTime = getTimer();
			for (i = 0; i < ITERATIONS; ++i)
			{
				plain25(
					1, 2, 3, 4, 5,
					6, 7, 8, 9, 10,
					11, 12, 13, 14, 15,
					16, 17, 18, 19, 20,
					21, 22, 23, 24, dummyArray
				);
			}
			afterTime = getTimer();
			log("\tplain: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < ITERATIONS; ++i)
			{
				varArgs(
					1, 2, 3, 4, 5,
					6, 7, 8, 9, 10,
					11, 12, 13, 14, 15,
					16, 17, 18, 19, 20,
					21, 22, 23, 24, dummyArray
				);
			}
			afterTime = getTimer();
			log("\tvar args: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < ITERATIONS; ++i)
			{
				arguments25(
					1, 2, 3, 4, 5,
					6, 7, 8, 9, 10,
					11, 12, 13, 14, 15,
					16, 17, 18, 19, 20,
					21, 22, 23, 24, dummyArray
				);
			}
			afterTime = getTimer();
			log("\targuments: " + (afterTime-beforeTime));
		}
 
		private function plain1(a:Array): void
		{
			a[0];
		}
 
		private function plain25(
			a:int, b:int, c:int, d:int, e:int,
			f:int, g:int, h:int, i:int, j:int,
			k:int, l:int, m:int, n:int, o:int,
			p:int, q:int, r:int, s:int, t:int,
			u:int, v:int, w:int, x:int, y:Array
		): void
		{
			y[0];
		}
 
		private function varArgs(...args): void
		{
			args[0];
		}
 
		private function arguments1(a:Array): void
		{
			arguments[0];
		}
 
		private function arguments25(
			a:int, b:int, c:int, d:int, e:int,
			f:int, g:int, h:int, i:int, j:int,
			k:int, l:int, m:int, n:int, o:int,
			p:int, q:int, r:int, s:int, t:int,
			u:int, v:int, w:int, x:int, y:Array
		): void
		{
			arguments[0];
		}
	}
}

This test has been designed such that each test function (plain, var args, arguments) indexes an Array. There is a 1-argument and 25-argument version of each of these, but the function bodies are the same. In the case of “plain”, an Array is passed as a parameter, the “var args” version indexes into the named arguments and the “arguments” version indexes into the arguments keyword, which represents the passed parameters. All of these functions’ bodies should perform identically.

I’ve written before about how the “var args” version will be slower than the “plain” version, so that should come as no surprise. Since var args can be used as an alternative to the arguments keyword, we should be able to see if arguments is any faster or slower than var args.

Before we look at the performance test results, let’s look at the bytecode of each test function using nemo440:

function private::plain1(Array):void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=9
    0       getlocal0     	
    1       pushscope     	
    2       getlocal1     	
    3       pushbyte      	0
    5       getproperty   	null
    7       pop           	
    8       returnvoid    	
  }
 
 
  function private::plain25(int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,Array):void	/* disp_id 0*/
  {
    // local_count=26 max_scope=1 max_stack=2 code_len=10
    0       getlocal0     	
    1       pushscope     	
    2       getlocal      	25
    4       pushbyte      	0
    6       getproperty   	null
    8       pop           	
    9       returnvoid    	
  }
 
 
  function private::varArgs():void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=9
    0       getlocal0     	
    1       pushscope     	
    2       getlocal1     	
    3       pushbyte      	0
    5       getproperty   	null
    7       pop           	
    8       returnvoid    	
  }
 
 
  function private::arguments1(Array):void	/* disp_id 0*/
  {
    // local_count=3 max_scope=1 max_stack=2 code_len=9
    0       getlocal0     	
    1       pushscope     	
    2       getlocal2     	
    3       pushbyte      	0
    5       getproperty   	null
    7       pop           	
    8       returnvoid    	
  }
 
 
  function private::arguments25(int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,Array):void	/* disp_id 0*/
  {
    // local_count=27 max_scope=1 max_stack=2 code_len=10
    0       getlocal0     	
    1       pushscope     	
    2       getlocal      	26
    4       pushbyte      	0
    6       getproperty   	null
    8       pop           	
    9       returnvoid    	
  }

Each test function looks nearly as simple as it does in AS3. The only exception, which I noted in the last article on this topic, is that the versions using the arguments keyword have one more local in their “local_count” to account for the arguments variable. It’s important to note here that no copying is evident from the bytecode disassembly. Now let’s see the difference in performance:

Environment Plain (1) Var Args (1) arguments (1) Plain (25) Var Args (25) arguments (25)
3.0 Ghz Intel Core 2 Duo, Windows XP 10 379 493 21 825 1148
2.0 Ghz Intel Core 2 Duo, Mac OS X 10.5 17 579 793 35 1495 1996

Here are some things to take away from these numbers:

  • We see once again that var args are slow. They’re about 40 times slower in both the 1-argument and 25-argument cases.
  • The 25-argument versions are about twice as slow as their 1-argument equivalents.
  • The 1-argument and 25-argument “arguments” versions was 30-40% slower than the respective “var args” versions and about 50 times slower than the plain version
  • Windows and Mac OS performance are relatively the same

In short, using arguments triggers a behind-the-scenes copy of your function’s parameters into an Array and causes it to be 50-55 times slower than a function not using arguments. This is even slower than using var args in a function and is magnified as you add more parameters. So be very careful about using the arguments keyword in any performance-critical code: your already-slow function calls will suddenly be 50 times slower!