Function Performance Update
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:
- Added tests for calling static functions via a class (i.e.
MyClass.foo()
) - 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.
- Split function variable testing in two: calling a function variable backed by a plain function and backed by a private method
- Added calls to a plain function and a private method via the
call
andapply
methods ofFunction
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 thecall
orapply
methods ofFunction
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 notfinal
- 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
- Access specifier (e.g.
- While the “slow” class of function types are all indeed slow, calling non-dynamic functions like private methods via
call
orapply
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.
#1 by Pavel fljot on April 23rd, 2012 ·
Hei. May I “request” function calls via custom namespaces (which might be a common for libraries/frameworks)? Function call in a custom namespace on specific (strong typed) object and on interface-typed object.
#2 by jackson on April 23rd, 2012 ·
Good catch! While I’ve covered that elsewhere, I didn’t add it to this test. It’s on my list for “Function Performance Update… Update”. :)
#3 by Rackdoll on April 23rd, 2012 ·
Nice one.
Am glad they pumped up the perfomance on static function calls.
Good work Jackson.
Keep on rockin! :)
Rackdoll