With access specifiers, statics, plain functions, and overriding, there are a lot of ways you can dress up a function in AS3. But how many programmers really know the performance implications of these options? Read on to find a straightforward test showing just that. EDIT: added functions defined in interfaces, getters, setters, and final functions.
In this little test app I’ve simply created empty version of a bunch of permutations of the options you have when making a function. First a little base class so we have something to override:
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 {} } }
And then the main test app:
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 FunctionPerformanceTest 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; /** * Application entry point */ public function FunctionPerformanceTest() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; __logger = new TextField(); __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); addEventListener(Event.ENTER_FRAME, testDynamicFunctions); } private function testDynamicFunctions(ev:Event): void { var beforeTime:int; var i:int; const NUM_ITERATIONS:int = 10000000; function localFunction(): void {} var functionVarFunction:Function = function(): void {} beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { plainFunction(); } log("Plain: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { localFunction(); } log("Local: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { functionVarFunction(); } log("Function var: " + (getTimer()-beforeTime)); log(""); removeEventListener(Event.ENTER_FRAME, testDynamicFunctions); addEventListener(Event.ENTER_FRAME, testMethods); } 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(); } log("Private: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { protectedFunction(); } log("Protected: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { internalFunction(); } log("Internal: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { publicFunction(); } log("Public: " + (getTimer()-beforeTime)); log(""); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { this.privateFunction(); } log("this.Private: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { this.protectedFunction(); } log("this.Protected: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { this.internalFunction(); } log("this.Internal: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { this.publicFunction(); } log("this.Public: " + (getTimer()-beforeTime)); log(""); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { staticPrivateFunction(); } log("Static private: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { staticProtectedFunction(); } log("Static protected: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { staticInternalFunction(); } log("Static internal: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { staticPublicFunction(); } log("Static public: " + (getTimer()-beforeTime)); log(""); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { overrideProtectedFunction(); } log("Override protected: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { overrideInternalFunction(); } log("Override internal: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { overridePublicFunction(); } log("Override public: " + (getTimer()-beforeTime)); log(""); 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(); } log("Super protected: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { super.overrideInternalFunction(); } log("Super internal: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { super.overridePublicFunction(); } log("Super public: " + (getTimer()-beforeTime)); log(""); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { interfaceFunction(); } log("Interface direct: " + (getTimer()-beforeTime)); var inter:IInterface = this; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { inter.interfaceFunction(); } log("Interface via interface: " + (getTimer()-beforeTime)); var clazz:FunctionPerformanceTest = this; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { clazz.interfaceFunction(); } log("Interface via class: " + (getTimer()-beforeTime)); log(""); 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; } log("Private Get: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { protectedGetFunction; } log("Protected Get: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { internalGetFunction; } log("Internal Get: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { publicGetFunction; } log("Public Get: " + (getTimer()-beforeTime)); log(""); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { privateSetFunction = 0; } log("Private Set: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { protectedSetFunction = 0; } log("Protected Set: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { internalSetFunction = 0; } log("Internal Set: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { publicSetFunction = 0; } log("Public Set: " + (getTimer()-beforeTime)); log(""); 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(); } log("Final Private: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { finalProtectedFunction(); } log("Final Protected: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { finalInternalFunction(); } log("Final Internal: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { finalPublicFunction(); } log("Final Public: " + (getTimer()-beforeTime)); log(""); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { finalOverrideProtectedFunction(); } log("Final Override protected: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { finalOverrideInternalFunction(); } log("Final Override internal: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { finalOverridePublicFunction(); } log("Final Override public: " + (getTimer()-beforeTime)); removeEventListener(Event.ENTER_FRAME, testFinals); } private function log(msg:*): void { __logger.appendText(msg + "\n"); } } } function plainFunction(): void {} interface IInterface { function interfaceFunction(): void; }
The output I see is never dependent on the access specifier (private, protected, internal, public), calling through this, the final keyword, or the presence of a get or set keyword, which is good. Here are results I get with access specifiers and this calls stripped out:
| Test | 3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP |
|---|---|
| Plain | 1969 |
| Local | 625 |
| Var | 670 |
| Method | 48 |
| Static | 60 |
| Override | 48 |
| super | 87 |
| Interface Direct | 48 |
| Interface via Interface | 65 |
| Interface via Class | 46 |
Immediately we see the clear outliers: plain functions, local functions, and Function variables. While I’ve talked about the slowness of these before, it’s good to see some confirmation. These are between 22 and 7 times as slow as the next slowest form of function call: calls through super. These in turn are 45% slower than statics and calls through an interface, which are 25% slower than everything else: methods, methods accessed through this, and overridden methods.
If you want speed you should not be using plain functions, local functions, or Function variables. This should give some hint as to the speed of Flash’s package-level functions like getTimer(). Further, you can get big speedups by foregoing static functions, calls trough an interface, and calls through super, especially if you’re going to be using that convenience a lot. The rest of the functions are as fast as you can get. Don’t let anyone tell you that access specifiers matter, calling through this adds needless slowdown, functions should be final for speed, or overriding is handy but slow. It is handy, but it’s not slow.
Keep the above speeds in the back of your mind as you architect and implement your AS3 programs. It may save you some optimization time down the line.
#1 by Joa Ebert on October 30th, 2009 · | Quote
Great effort on the tests but I can not agree on all your results.
First of all the this modifier is added to every method. So your difference between methods including “this” and without “this” is based on your testing. They result in the exact same bytecode: every method is actually called like “this.method()”.
Second: Overriding is not slow, if you do not call the super method of course. But in general, you do. Which also means you have the method call plus the super call from within the method.
An interesting test would be also the difference between those two calls:
var x: IInterface = object;
var y: ClassImplementingIInterface = object;
x.interfaceMethod();
y.interfaceMethod();
#2 by jackson on October 30th, 2009 · | Quote
I’ve updated the article for the calls through this to drop them from the table and explicitly point out that there is no statistical relevance to using this when making the function call. Previously I simply didn’t discuss it, but this way is better. Thanks for pointing it out.
As for interfaces, I did the test and updated the article with this. Surprisingly, calling through an interface variable is significantly more expensive than calling directly or through a class variable. Thanks for pointing this out!
PS: I also added getters and setters, but there’s no statistical difference there either.
#3 by Troy Gilbert on October 30th, 2009 · | Quote
Would be interesting to see a singleton (or monostate) thrown in as a comparison to statics, i.e. MyClass.getInstance().doMethod() vs. MyClass.doMethod(). Singletons may actually be good for something! ;-)
#4 by jackson on October 30th, 2009 · | Quote
Interesting idea, but it’s a little off-topic for the main article so I’ll just post it as a response. Here are a couple of simple Singleton classes:
And here are some tests to add to the test function from the article:
I get:
So the statics are much faster… on the surface. The advantage to the instance getter is that it allows you to de-static the rest of the work that will be done by the function. For example, if the doMethod() wants to call doOtherStuff(), that would also be a static function call since doMethod() has no this to call through. This is not the case with the instance getter approach. So I think the usual order where classes of utility methods (eg. a MathUtils) are built of all static methods rather than an instance getter or plain functions in a package and a singleton as a class with a static instance variable and getter is right for performance as well.
#5 by benji on March 1st, 2010 · | Quote
might be fairer to store the instance outside of the loop:
#6 by jackson on March 1st, 2010 · | Quote
Very good point. This is exactly the kind of advantage you get going the “instance getter” way that you can’t take advantage of the “static” way. Thanks for the sample code showing this.
#7 by Sev on October 30th, 2009 · | Quote
Heya Jackson. Thanks for these tests!
I’m quite shocked by the performance of plain functions. Package functions are a litte more than double as fast – better said half as slow ;-) Still.. I totally haven’t expected that.
As for the other tests (static, internal, private, interface etc.) I have seen a *much* slower difference in my testing. Could it be that you’ve tested with the Debug Player? Debug Player often gives pretty different results..
Just my two cents :-)
#8 by jackson on October 30th, 2009 · | Quote
Nope, I always test with the release player. Could you post your times with the release player as well as system and OS specs?
#9 by Andre Michelle on October 30th, 2009 · | Quote
You test in the Releaseplayer? In your code you use ‘trace’ to test the performance.
#10 by jackson on October 30th, 2009 · | Quote
When I actually test it I use a TextField to log, but the usage of that TextField is not timed. I switch it to trace for the posting to make the test clearer to readers. Perhaps in the future I’ll leave in the logging to a TextField.
#11 by Sev on October 30th, 2009 · | Quote
Here we go (Mac OS X, 10.6.1, 10,0,22,87, Core 2 Duo 2.4):
Plain: 2436
Local: 789
Function var: 808
Private: 78
Protected: 74
Internal: 74
Public: 73
this.Private: 71
this.Protected: 73
this.Internal: 76
this.Public: 78
Static private: 76
Static protected: 75
Static internal: 75
Static public: 78
Override protected: 73
Override internal: 80
Override public: 75
Super protected: 68
Super internal: 79
Super public: 68
Interface direct: 75
Interface via interface: 82
Interface via class: 67
Package level function: 1102
(from several tests I’d say +-8% accuracy) :)
#12 by jackson on October 30th, 2009 · | Quote
Thanks for posting these! I’m surprised that static calls and super method calls are so fast on your environment. Maybe it’s a difference in Flash between Windows and Mac. I’ll try it on my Mac and post updated numbers. Thanks again for this.
#13 by Karl Knocking on November 2nd, 2009 · | Quote
Thank you for the testing, I always thougt static methods were slow… :)
#14 by jackson on November 2nd, 2009 · | Quote
You’re welcome… and you were right! :)
#15 by dVyper on November 3rd, 2009 · | Quote
Static access can still be slow:
http://blog.controul.com/2009/04/how-slow-is-static-access-in-as3avm2-exactly/
(an article inspired by a comment by me hehe)
#16 by jackson on November 3rd, 2009 · | Quote
Thanks for the link. The article is quite good!
#17 by Joa Ebert on November 5th, 2009 · | Quote
It is no surprise that calling an interface method is slower when using the interface type instead of the direct class. But finally some evidence! The same applies to Java by the way as well.
Best,
Joa
#18 by Nicolas on November 10th, 2009 · | Quote
Did you check the speed of final functions? Apparently the should be faster, because they cannot be overriden.
#19 by jackson on November 10th, 2009 · | Quote
Good point! I’ve updated the article with functions defined with the final keyword. It didn’t make any speed difference, so I noted that in the article. I also added TextField-based logging and spread the tests out over frames since it was getting to the point where I was getting 15 second timeouts. Thanks for the tip!
#20 by Tim Oxley on November 10th, 2009 · | Quote
What about anonymous functions? Though we could expect this to be mostly the same as a Function variable, you never know.
#21 by jackson on November 10th, 2009 · | Quote
There are tests of local functions and function variables already. Did you mean something different? If so, could you provide a brief bit of code showing what? Thanks.
#22 by TwoFace on November 24th, 2009 · | Quote
Hmm, thanks for this great article! BTW I’m wondering what about AS2 ?
#23 by jackson on November 24th, 2009 · | Quote
You’re very welcome! If you port the test to AS2, I’d be happy to post it.
#24 by Restore Deleted Files on February 21st, 2010 · | Quote
Did you check the speed of final functions? Apparently the should be faster, because they cannot be overriden.
#25 by jackson on February 21st, 2010 · | Quote
Yep, but they’re no different than non-final functions. See my paragraph before the results table: