Above all others, there is one article I refer back to most: 2009’s Function Performance. It was updated for Flash Player 10.1 and 10.2, but not 10.3, 11.0, 11.1, or 11.2. Today I’m updating this article for Flash Player 11.2, adding some missing function types, and including a set of graphs to make for the ultimate function performance reference.

Essentially, the goal is to test every type of function there is in AS3 and every way you’d want to call them. I already had the basics like static and non-static methods, interfaces, and local functions. Now I’m making some updates:

  1. Added tests for calling static functions via a class (i.e. MyClass.foo())
  2. Split apart “dynamic” function tests: local function, function variable, “plain” function (i.e. in the package but not in the class). Thanks to Skyboy for the tip.
  3. Split function variable testing in two: calling a function variable backed by a plain function and backed by a private method
  4. Added calls to a plain function and a private method via the call and apply methods of Function

Here’s the source code for the updated performance test:

// Base.as:
package
{
	import flash.display.Sprite;
 
	public class Base extends Sprite
	{
		protected function overrideProtectedFunction(): void {}
		internal function overrideInternalFunction(): void {}
		public function overridePublicFunction(): void {}
 
		protected function finalOverrideProtectedFunction(): void {}
		internal function finalOverrideInternalFunction(): void {}
		public function finalOverridePublicFunction(): void {}
	}
}
// IInterface.as:
package
{
	public interface IInterface
	{
		function interfaceFunction(): void;
	}
}
// FunctionPerformanceUpdate.as:
package
{
	import flash.display.*;
	import flash.events.Event;
	import flash.text.*;
	import flash.utils.*;
 
	/**
	*   An app to test function call performance
	*   @author Jackson Dunstan
	*/
	public class FunctionPerformanceUpdate extends Base implements IInterface
	{
		private function privateFunction(): void {}
		protected function protectedFunction(): void {}
		internal function internalFunction(): void {}
		public function publicFunction(): void {}
 
		private function get privateGetFunction(): int { return 0; }
		protected function get protectedGetFunction(): int { return 0; }
		internal function get internalGetFunction(): int { return 0; }
		public function get publicGetFunction(): int { return 0; }
 
		private function set privateSetFunction(val:int): void {}
		protected function set protectedSetFunction(val:int): void {}
		internal function set internalSetFunction(val:int): void {}
		public function set publicSetFunction(val:int): void {}
 
		private static function staticPrivateFunction(): void {}
		protected static function staticProtectedFunction(): void {}
		internal static function staticInternalFunction(): void {}
		public static function staticPublicFunction(): void {}
 
		override protected function overrideProtectedFunction(): void {}
		override internal function overrideInternalFunction(): void {}
		override public function overridePublicFunction(): void {}
 
		final private function finalPrivateFunction(): void {}
		final protected function finalProtectedFunction(): void {}
		final internal function finalInternalFunction(): void {}
		final public function finalPublicFunction(): void {}
 
		final override protected function finalOverrideProtectedFunction(): void {}
		final override internal function finalOverrideInternalFunction(): void {}
		final override public function finalOverridePublicFunction(): void {}
 
		public function interfaceFunction(): void {}
 
		private var __logger:TextField; 
 
		public function FunctionPerformanceUpdate()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			__logger = new TextField();
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			row("Function Type", "Time");
 
			addEventListener(Event.ENTER_FRAME, testPlainFunction);
		}
 
		private function testPlainFunction(ev:Event): void
		{
			var beforeTime:int;
			var i:int;
			const NUM_ITERATIONS:int = 10000000;
 
			function localFunction(): void {}
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				plainFunction();
			}
			row("Plain", (getTimer()-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testPlainFunction);
			addEventListener(Event.ENTER_FRAME, testLocalFunction);
		}
 
		private function testLocalFunction(ev:Event): void
		{
			var beforeTime:int;
			var i:int;
			const NUM_ITERATIONS:int = 10000000;
 
			function localFunction(): void {}
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				localFunction();
			}
			row("Local", (getTimer()-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testLocalFunction);
			addEventListener(Event.ENTER_FRAME, testFunctionVar);
		}
 
		private function testFunctionVar(ev:Event): void
		{
			doTestFunctionVar(plainFunction, "Function var (Plain)");
			doTestFunctionVar(privateFunction, "Function var (Private)");
 
			removeEventListener(Event.ENTER_FRAME, testFunctionVar);
			addEventListener(Event.ENTER_FRAME, testCallApply);
		}
 
		private function testCallApply(ev:Event): void
		{
			var beforeTime:int;
			var i:int;
			const NUM_ITERATIONS:int = 10000000;
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				plainFunction.call();
			}
			row("call (Plain)", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				privateFunction.call();
			}
			row("call (Private)", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				plainFunction.apply();
			}
			row("apply (Plain)", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				privateFunction.apply();
			}
			row("apply (Private)", (getTimer()-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testCallApply);
			addEventListener(Event.ENTER_FRAME, testMethods);
		}
 
		private function doTestFunctionVar(func:Function, name:String): void
		{
			var beforeTime:int;
			var i:int;
			const NUM_ITERATIONS:int = 10000000;
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				func();
			}
			row(name, (getTimer()-beforeTime));
		}
 
		private function testMethods(ev:Event): void
		{
			var beforeTime:int;
			var i:int;
			const NUM_ITERATIONS:int = 10000000;
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				privateFunction();
			}
			row("Private", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				protectedFunction();
			}
			row("Protected", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				internalFunction();
			}
			row("Internal", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				publicFunction();
			}
			row("Public", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				this.privateFunction();
			}
			row("this.Private", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				this.protectedFunction();
			}
			row("this.Protected", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				this.internalFunction();
			}
			row("this.Internal", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				this.publicFunction();
			}
			row("this.Public", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				staticPrivateFunction();
			}
			row("Static private", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				staticProtectedFunction();
			}
			row("Static protected", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				staticInternalFunction();
			}
			row("Static internal", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				staticPublicFunction();
			}
			row("Static public", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				FunctionPerformanceUpdate.staticPrivateFunction();
			}
			row("Static private (by class)", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				FunctionPerformanceUpdate.staticProtectedFunction();
			}
			row("Static protected (by class)", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				FunctionPerformanceUpdate.staticInternalFunction();
			}
			row("Static internal (by class)", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				FunctionPerformanceUpdate.staticPublicFunction();
			}
			row("Static public (by class)", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				overrideProtectedFunction();
			}
			row("Override protected", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				overrideInternalFunction();
			}
			row("Override internal", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				overridePublicFunction();
			}
			row("Override public", (getTimer()-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testMethods);
			addEventListener(Event.ENTER_FRAME, testSupersAndInterfaces);
		}
 
		private function testSupersAndInterfaces(ev:Event): void
		{
			var beforeTime:int;
			var i:int;
			const NUM_ITERATIONS:int = 10000000;
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				super.overrideProtectedFunction();
			}
			row("Super protected", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				super.overrideInternalFunction();
			}
			row("Super internal", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				super.overridePublicFunction();
			}
			row("Super public", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				interfaceFunction();
			}
			row("Interface direct", (getTimer()-beforeTime));
 
			var inter:IInterface = this;
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				inter.interfaceFunction();
			}
			row("Interface via interface", (getTimer()-beforeTime));
 
			var clazz:FunctionPerformanceUpdate = this;
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				clazz.interfaceFunction();
			}
			row("Interface via class", (getTimer()-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testSupersAndInterfaces);
			addEventListener(Event.ENTER_FRAME, testGettersAndSetters);
		}
 
		private function testGettersAndSetters(ev:Event): void
		{
			var beforeTime:int;
			var i:int;
			const NUM_ITERATIONS:int = 10000000;
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				privateGetFunction;
			}
			row("Private Get", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				protectedGetFunction;
			}
			row("Protected Get", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				internalGetFunction;
			}
			row("Internal Get", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				publicGetFunction;
			}
			row("Public Get", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				privateSetFunction = 0;
			}
			row("Private Set", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				protectedSetFunction = 0;
			}
			row("Protected Set", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				internalSetFunction = 0;
			}
			row("Internal Set", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				publicSetFunction = 0;
			}
			row("Public Set", (getTimer()-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testGettersAndSetters);
			addEventListener(Event.ENTER_FRAME, testFinals);
		}
 
		private function testFinals(ev:Event): void
		{
			var beforeTime:int;
			var i:int;
			const NUM_ITERATIONS:int = 10000000;
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				finalPrivateFunction();
			}
			row("Final Private", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				finalProtectedFunction();
			}
			row("Final Protected", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				finalInternalFunction();
			}
			row("Final Internal", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				finalPublicFunction();
			}
			row("Final Public", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				finalOverrideProtectedFunction();
			}
			row("Final Override protected", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				finalOverrideInternalFunction();
			}
			row("Final Override internal", (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				finalOverridePublicFunction();
			}
			row("Final Override public", (getTimer()-beforeTime));
 
			removeEventListener(Event.ENTER_FRAME, testFinals);
		}
 
		private function row(...cols): void
		{
			__logger.appendText(cols.join(",") + "\n");
		}
	}
}
function plainFunction(): void {}

I ran this test on the following environment:

  • Flex SDK (MXMLC) 4.6.0.23201, compiling in release mode (no debugging or verbose stack traces)
  • Release version of Flash Player 11.2.202.229
  • 2.4 Ghz Intel Core i5
  • Mac OS X 10.7.3

And got these results:

Function Type Time
Plain 432
Local 344
Function var (Plain) 308
Function var (Private) 216
call (Plain) 427
call (Private) 859
apply (Plain) 311
apply (Private) 834
Private 56
Protected 57
Internal 60
Public 59
this.Private 55
this.Protected 60
this.Internal 59
this.Public 54
Static private 67
Static protected 65
Static internal 61
Static public 64
Static private (by class) 68
Static protected (by class) 69
Static internal (by class) 71
Static public (by class) 70
Override protected 61
Override internal 60
Override public 59
Super protected 66
Super internal 63
Super public 62
Interface direct 54
Interface via interface 53
Interface via class 56
Private Get 64
Protected Get 60
Internal Get 59
Public Get 58
Private Set 60
Protected Set 61
Internal Set 61
Public Set 58
Final Private 54
Final Protected 56
Final Internal 59
Final Public 61
Final Override protected 58
Final Override internal 56
Final Override public 59

Here are the graphs of these results. The first is for all functions, the second for just the “fast” functions (there is an obvious divide), and the third for just the “slow” functions.

While much of these results remains roughly the same as back in Flash Player 10.0-10.2, much has changed as well:

  • Even since Flash Player 11.1 in January, we now see calling static methods via a class name (i.e. MyClass.foo() is just as quick as not using the class name.
  • Using a Function-typed variable (e.g. as with most callbacks) or using the call or apply methods of Function dramatically slows down the function call (by at least 2x).
  • Most of the “fast” functions are about the same speed. There is no appreciable difference between any of the following:
    • Access specifier (e.g. private, public
    • final or not final
    • Calling through the this object
    • Getters/Setters vs. other methods
    • Overriding functions vs. functions that don’t override
    • Static vs. non-static (through a class name or otherwise)
    • Calling through the super object
  • While the “slow” class of function types are all indeed slow, calling non-dynamic functions like private methods via call or apply are much (4x) slower.

I hope this article serves as a good reference when you’re considering the performance of a certain type of function; I know its predecessor served me well.

If you’ve spotted a bug or have a suggestion, please feel free to leave a comment.