Function Performance
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 ·
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 ·
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 ·
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 ·
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 ·
might be fairer to store the instance outside of the loop:
#6 by jackson on March 1st, 2010 ·
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 ·
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 ·
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 ·
You test in the Releaseplayer? In your code you use ‘trace’ to test the performance.
#10 by jackson on October 30th, 2009 ·
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 ·
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 ·
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 ·
Thank you for the testing, I always thougt static methods were slow… :)
#14 by jackson on November 2nd, 2009 ·
You’re welcome… and you were right! :)
#15 by dVyper on November 3rd, 2009 ·
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 ·
Thanks for the link. The article is quite good!
#17 by Joa Ebert on November 5th, 2009 ·
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 ·
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 ·
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 ·
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 ·
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 ·
Hmm, thanks for this great article! BTW I’m wondering what about AS2 ?
#23 by jackson on November 24th, 2009 ·
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 ·
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 ·
Yep, but they’re no different than non-final functions. See my paragraph before the results table:
#26 by grow taller on September 5th, 2010 ·
My health could be the most significant thing, and I dunno about you guys, but I have spent thousands on my health. This is because I believe if you’re healthy, then you’ll be able to have any task and woman/man you need since you believe confident. One of the main elements I did for my health was learn to grow taller naturally. I went for the link in my name and it’s taught me some beneficial methods to grow taller naturally without any pricey treatments or supplements, verify it out if you want :)
#27 by jonathanasdf on January 1st, 2011 ·
Hey, I have 2 classes A and B. B needs to call a function on A, but must not know of A’s existence. A however knows of B’s existence. Here are 2 ideas I came up with:
1. A passes a closure to B and B calls that
2. A registers an event listener and B dispatches an event. The event however is a custom event since I need to pass some parameters as well.
which is faster?
#28 by skyboy on January 1st, 2011 ·
The closure would be the fastest, however, if you go that route you may as well make an interface for it, which is around 10x faster; But the event method is the one that actually holds to the B not knowing of A, at least as far as your own code goes: FP code knows A exists.
A function call via an interface would be the simplest, fastest, and equally extensible route to go.
#29 by jackson on January 1st, 2011 ·
Skyboy is correct. #1 will get you “local” speed from the table above. #2 will be terrible in performance, as shown by TurboSignals. Skyboy’s recommendation, which I agree with given your constraints, will get you “interface via interface” speed from the table above. The gap has closed in Flash Player 10.1 though, so check out the update article.
#30 by jonathanasdf on January 2nd, 2011 ·
Thanks. I hadn’t thought of using an interface, but it seems to fit my purposes excellently.
#31 by ohai on June 5th, 2011 ·
Something seems wrong with the code, I’m getting similar results for every test. This is using the newest Flash Player:
WIN 10,3,181,14
I copy/pasted the code exactly.
#32 by jackson on June 5th, 2011 ·
I’m not sure what issue you’re having here. Are you saying that you’re getting the same performance for every type of function? If so, are you sure you’re compiling without debug and verbose stack traces and, even more importantly, running in a release player rather than a debug player? In my Flash Player 10.2 Performance Followup, I was still seeing quite varied results. I wouldn’t expect much change in 10.3…
#33 by ohai on June 5th, 2011 ·
yeah, it seems I was running IE9 with the debug player.
of note, with the debug player installed on IE9 it won’t run the release version of the code.
where with Chrome, the debug or the release version of the code, is the same and fast.
#34 by ohai on June 5th, 2011 ·
It seems IE9 running Flash Debug Player is exhaustively slow. And runs everything the same.
Chrome has these stats:
WIN 10,3,181,14
Plain: 391
Local: 314
Function var: 320
Private: 80
Protected: 73
Internal: 74
Public: 76
this.Private: 76
this.Protected: 72
this.Internal: 77
this.Public: 76
Static private: 79
Static protected: 89
Static internal: 79
Static public: 79
Override protected: 77
Override internal: 74
Override public: 72
Super protected: 73
Super internal: 77
Super public: 74
Interface direct: 76
Interface via interface: 77
Interface via class: 81
Private Get: 71
Protected Get: 70
Internal Get: 70
Public Get: 69
Private Set: 75
Protected Set: 73
Internal Set: 71
Public Set: 71
Final Private: 71
Final Protected: 72
Final Internal: 73
Final Public: 77
Final Override protected: 69
Final Override internal: 70
Final Override public: 70
#35 by jackson on June 5th, 2011 ·
Ah, there you go then. Debug players are useless for performance testing as they perform wildly differently than release players, which are actually what users run. Perhaps I should put a simple one-liner debug player check in all my performance tests…
#36 by ohai on June 5th, 2011 ·
That would help a lot. I forget when I’m running the debug player.
Here’s Chrome again, using the release version of the code:
I can conclude its best to use Final Internal or this.Protected as the fastest ways to architect code?
Plain: 394
Local: 318
Function var: 310
Private: 73
Protected: 80
Internal: 76
Public: 80
this.Private: 73
this.Protected: 70
this.Internal: 76
this.Public: 78
Static private: 89
Static protected: 82
Static internal: 82
Static public: 79
Override protected: 70
Override internal: 81
Override public: 76
Super protected: 76
Super internal: 78
Super public: 80
Interface direct: 76
Interface via interface: 79
Interface via class: 76
Private Get: 73
Protected Get: 71
Internal Get: 70
Public Get: 76
Private Set: 75
Protected Set: 73
Internal Set: 72
Public Set: 72
Final Private: 72
Final Protected: 72
Final Internal: 67
Final Public: 72
Final Override protected: 72
Final Override internal: 70
Final Override public: 70
#37 by jackson on June 5th, 2011 ·
There will naturally be some “wiggle” in your test results. Try running the test several times and seeing how the results vary. I think the differences between access specifier (
public
,private
, etc.) andfinal
/notfinal
are minor testing anomalies: they don’t actually change the performance.#38 by JP Stringham on June 24th, 2011 ·
For the sake of interest and with all the fuss these days about Flash on iOS, I decided to execute this script on an iPhone 4. I had to make a couple modifications.
1) The NUM_ITERATIONS was made into a private static const.
2) NUM_ITERATIONS was DECREASED by an order of magnitude. Execution times were still abysmal.
Plain: 19886
Local: 16043
Function var: 16187
Private: 5584
Protected: 5622
Internal: 6017
Public: 6492
this.Private: 5524
this.Protected: 5624
this.Internal: 5780
this.Public: 5662
Static private: 5818
Static protected: 5632
Static internal: 5638
Static public: 5743
Override protected: 5856
Override internal: 5911
Override public: 5695
Super protected: 5674
Super internal: 5853
Super public: 5901
Interface direct: 5510
Interface via interface: 5550
Interface via class: 5514
Private Get: 6563
Protected Get: 6172
Internal Get: 5844
Public Get: 5644
Private Set: 16141
Protected Set: 18751
Internal Set: 16383
Public Set: 15935
Final Private: 56311
Final Protected: 56317
Final Internal: 56242
Final Public: 56032
Final Override protected: 56237
Final Override internal: 55356
Final Override public: 55609
Built for iOS from Flash Builder 4.5.1 targeting AIR 2.7. Interestingly this flies completely in the face of the optimization ‘wisdom’ preached by Adobe themselves. And no, those last results aren’t typos, I had to wait several minutes before I saw any results.
#39 by jackson on June 24th, 2011 ·
Interesting results. It’s a shame to see such poor performance. However, I have a couple of suggestions that may speed these times up:
1) Make sure you’re using a release build. ActionScript typically runs 2-3x slower in debug builds.
2) A static field lookup every loop will be very expensive and greatly increase loop times overall. It’s annoying to have to keep declaring NUM_ITERATIONS locally, but local variable lookup is really worth it.
Hope that helps.
#40 by JP Stringham on June 24th, 2011 ·
Sorry about that. You’re absolutely right. Also the discrepancy in my first test for Final functions was because NUM_ITERATIONS still existed as a local field. Please (if possible) delete or severely edit my first post. Here are updated, more accurate numbers.
Again this is still with NUM_ITERATIONS set to 1000000, one order of magnitude. I have triple checked and this is now consistent with all tests – what I’ve done is set NUM_ITERATIONS as a local const and then done a local function ‘cache’ as you illustrated in your linked post where within the function, eg:
I humbly and profusely apologize for the horrible misinformation prior. It is still disappointing, however the performance drop is not nearly as jarring as before!
Plain: 6660
Local: 2335
Function var: 2307
Private: 245
Protected: 241
Internal: 238
Public: 247
this.Private: 237
this.Protected: 241
this.Internal: 238
this.Public: 232
Static private: 354
Static protected: 268
Static internal: 271
Static public: 261
Override protected: 236
Override internal: 246
Override public: 242
Super protected: 227
Super internal: 282
Super public: 294
Interface direct: 226
Interface via interface: 276
Interface via class: 222
Private Get: 234
Protected Get: 225
Internal Get: 222
Public Get: 222
Private Set: 2489
Protected Set: 5797
Internal Set: 2382
Public Set: 2505
Final Private: 244
Final Protected: 252
Final Internal: 288
Final Public: 352
Final Override protected: 226
Final Override internal: 223
Final Override public: 224
I performed these tests three times. Once I saw an anomalous doubling of this.Private, but that did not occur 2/3 times. However Protected Set is consistently twice as slow as its Internal, Private and Public counterparts.
#41 by jackson on June 24th, 2011 ·
No worries, we all make mistakes from time to time. My article this week included a silly typo that forced me to rewrite most of it. It’s no biggie. :)
As for your test results, they’re pretty much the same as my results except for the getters and setters. Keep in mind that the iPhone 4 is running a single-core 800 Mhz ARM chip and my test machine in the article was a 3.0 Ghz Intel Core 2 Duo. The iPhone’s also got an 800 Mhz memory bus compared to the 1066 Mhz dual-channel memory bus on the desktop test machine. Given such a huge hardware advantage on the side of the test machine from the article, I don’t think it’s so unreasonable to expect an order of magnitude less performance out of the iPhone. Still, you’re seeing a ~50x slowdown, so there’s a good chance that more is going on here. Perhaps the compiler used to compile the Flash Player VM is not as optimized for ARM as it is for x86. For example, Flash Player may be compiled with Intel’s highly-optimized compiler when targeting x86 and the much-slower GCC may be used when targeting ARM. Together, these hardware and software differences alone may be responsible for the difference.
Thanks for the performance numbers!
#42 by dimuMurray on August 14th, 2011 ·
How about accessing functions via the index operator?
Something like this:
Too slow?
#43 by dimuMurray on August 14th, 2011 ·
^ Should have read “How about invoking functions via the index operator”
#44 by skyboy on October 8th, 2011 ·
There are a couple test cases missing for this: a reference function var (not local, and shouldn’t be included in the dynamic function tests to avoid the overhead; plain should be moved out) and apply/call.
I just did the test to find out if caching apple/call would result in a speed up (in FP11: no. apply/call are the same speed as a dynamic call when using the dot notation). I just did a test, added the missing ones and graphed it, here: http://i.imgur.com/9ElgF.png with the code: http://pastebin.com/gyQ9swJD
#45 by skyboy on October 8th, 2011 ·
I posted the code/results and SWF in this thread: http://kongregate.com/forums/4/topics/211152
Includes some 10.3 results (which you missed doing an update on). Should get a really good representative sample of performance across platforms and CPUs (some really high end machines, low end machines like mine, various windows versions, linux. maybe some macs).
#46 by jackson on October 8th, 2011 ·
All good ideas for a follow-up article. :)