Runnables as Function Pointers
Last Friday’s article expressed some longing for C-style function pointers. It attempted to use AS3’s Namespace class to fake a function pointer. Unfortunately, this resulted in far slower code than simple direct access. Today’s article shows a technique that actually results in far faster code!
The Namespace approach failed due to Namespace being a dynamic class and access through it therefore being necessarily slow. So how about a non-dynamic approach? The idea I came up with was to create a class like Java’s Runnable interface. The idea is to create an interface that defines one function that takes the parameters you want to pass and returns the parameters you want returned. This is similar to function pointers in C where the syntax forces you to specify what the function’s parameters and return value are. The next step is to create a class implementing the interface for each function you want to point to. Next, simply implement the one function that the interface specifies. Lastly, the code wanting to emulate function pointers points its Runnable variable at an appropriate object implementing Runnable and calls to its one function are then made. Consider this simple example:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class RunnableTest extends Sprite { public function RunnableTest() { var logger:TextField = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); var i:int; const FUNC_CALL_REPS:int = 10000000; var runnable:Runnable = new MyRunnable(); var func:Function = runnable.run; var beforeTime:int = getTimer(); for (i = 0; i < FUNC_CALL_REPS; ++i) { func(); } logger.appendText("Func call time: " + (getTimer()-beforeTime) + "\n"); beforeTime = getTimer(); for (i = 0; i < FUNC_CALL_REPS; ++i) { runnable.run(); } logger.appendText("Runnable call time: " + (getTimer()-beforeTime) + "\n"); beforeTime = getTimer(); for (i = 0; i < FUNC_CALL_REPS; ++i) { this.foo(); } logger.appendText("Direct call time: " + (getTimer()-beforeTime)); } private function foo(): void { } } } internal interface Runnable { function run(): void } internal class MyRunnable implements Runnable { public function run(): void { } }
The results are impressive:
Machine | Function Object Time | Runnable Time | Direct Call Time |
---|---|---|---|
3.0 Ghz Intel Core 2 Duo with 2GB RAM on Windows XP | 613 | 43 | 41 |
2.2 Ghz Intel Core 2 Duo with 2GB RAM on Mac OS X 10.6 | 828 | 63 | 59 |
It seems as though you sacrifice almost no performance using this technique. Runnables only slightly lag behind direct function calls and are some 14x faster than Function objects! But what else do you lose? Well, you’ll need to create at least one interface and two classes, which contributes to SWF size, project complexity, and number of files to maintain. You’ll need to create more classes implementing the interface if you have more functions to point at. You’ll also need to instantiate an object of each of these types in order to point at it. If you are going to do a lot of processing, this probably won’t be much of a burden for the benefits (fewer if-else chains) you’ll get. You can also pre-allocate and re-use them. Lastly, you’ll need to split these functions out of the classes they naturally belong in and therefore need to pass along the “this” pointer they would normally have and provide non-private access to the class as far as the function needs to do its work. This can lead to overexposed classes, but the internal access specifier can help with that.
There is one surprising benefit of the runnable strategy as described above. If you intend to pass the same arguments to the function over and over, such as the “this” pointer it would normally have, you can simply store them as fields of the class. This leads to a related secondary advantage. Say your function normally computed a value and added it to an Array field of the class it should belong to when not using the runnable technique. You could pass this Array to the runnable’s constructor, store it as a field, and then share a reference to the Array with the original class. Both of these are good for limiting argument passing.
This technique works out surprisingly well. It’s a lot of overhead in typing, propagation of files, and a little overhead in SWF size, but when you really need to eliminate some if-else chains with a function pointer-style solution, runnables sure seem to be the way to go.
#1 by Troy Gilbert on September 25th, 2009 ·
You can get the same benefits without having to implement an interface. I’ve done some performance tests and any function reference (except for an anonymous function, what you’re calling a function object) will run at virtually the same speed of direct call.
For the latest rev of our API we’re using callbacks (Function references) for all our notifications because they’re significantly faster (10x+) than native events (because they require a memory allocation).
#2 by jackson on September 25th, 2009 ·
Could you elaborate on your technique? The three ways I know to do function pointers are:
The first two are painfully slow, as shown in this article and the linked one about Namespaces. The runnables technique has almost no performance penalty. If you know of a fourth technique, I’d sure like to hear about it.
I totally agree about Function variables being much faster than events. That really just shows how Function variables are relatively fast when compared to events, not that Function variables are absolutely fast when compared to all available techniques. For example, your API could use the runnable strategy for callbacks rather than Function variables. Consider a simple case:
That would definitely be faster than if the callback was a Function variable. Just a thought if you’re concerned enough about speed to ditch native events.
Thanks for commenting!
#3 by Troy Gilbert on September 25th, 2009 ·
I’m guessing when you do your performance tests of Function variables you’re assigning an anonymous function to them. Anonymous functions are slow (closure and such, I guess). But if you assign any normal, named function (class method, static or instance, package function, etc.) to a Function variable it executes at virtually the same speed as a direct function call.
#4 by jackson on September 25th, 2009 ·
My test is really just what you see above. I’m assigning a normal, named function to the Function variable on the line:
And the performance is terrible. More precisely, the performance of the function call is terrible, not the execution of the function.
Could you provide a test case that shows otherwise? The runnables technique does have downsides, as discussed in the article, so I’d really like to see if you have a better way in mind.
#5 by Troy Gilbert on September 25th, 2009 ·
I’ve got a test case that compares events, callbacks and observers. What’s the best way to post code to the blog (or I could email it to you)?
#6 by jackson on September 25th, 2009 ·
We might be talking about two different comparisons. I think you’re talking about events versus callbacks and I’m talking about callbacks via Function variables versus callbacks via runnables. I totally agree about events being slower than callbacks via Function variables and am trying to show that there’s a further speedup. It’s kind of like this:
This article is an attempt to improve even on callbacks via Function variables by implementing callbacks via runnables. There’s nothing limiting runnables to callbacks, so I covered it in general rather than just the callbacks case. Now I’m wondering if you have discovered a technique that improves even on callbacks via runnables. That would be awesome to see! You can either e-mail it to me directly or post it in comments by wrapping it up:
<pre lang="actionscript3">
// code goes here
</pre>
Thanks!
#7 by Troy Gilbert on September 25th, 2009 ·
Whoops, forgot the obvious: check out my blog post on the subject from a few weeks ago:
http://troygilbert.com/2009/09/events-vs-callbacks-revisited/
That link is to a correction on an earlier article. I haven’t had a chance to go back and write a fresh one cleaning everything up, but you’ll find examples (and perf tests) of callbacks.
#8 by Troy Gilbert on September 25th, 2009 ·
Okay, here’s my test harness:
It tries to be a slightly more real-world usage scenario. I allocate multiple objects to receive notification (via event, callback or observer), and iterate through the targets through a container (you call directly on a local instance, which will make it seem even faster).
Looking back at my times, there is a difference between callbacks (Function references) and observers (runnables). A not insignificant difference, to be honest, particularly if you look at the times in the standalone Flash Player (as opposed to debug browser plug-in). But when compared to events, callbacks and observers are in the same class.
All that being said, observers look to give a 2x-3x improvement over callbacks, but that’s only measurable over hundreds of thousands of calls. Given the huge drawback of having to implement a specific interface and being limited to a single function per-class, I think callbacks (Function references) are the winner.
#9 by Troy Gilbert on September 25th, 2009 ·
Well, some of its been eaten up by making it HTML-safe, so cut-n-paste with care!
#10 by jackson on September 25th, 2009 ·
Thanks for the demo. I edited your comment to fix the HTML safety stuff. My results on a 3.0Ghz Intel Core 2 Duo with 2GB of RAM on Windows XP are:
So right away the “observer” technique is 3x faster than callbacks, just like you say. It sure looks like your observer technique is almost the same as my runnable technique; perhaps the more correct terminology. Wikipedia seems to think so. In any case, it seems as though your observer technique test validates my runnable technique test, which is good to hear.
Thanks very much for providing the test!
#11 by Troy Gilbert on September 25th, 2009 ·
Wow, Flash Player on Windows performs so much better when it comes to memory allocations (the real difference between events and callbacks):
That’s the release standalone Flash Player 10, the fastest way to run Flash on the Mac.
#12 by Uwe Holland on September 25th, 2009 ·
I’m following your discussion interested and I’ve just posted our approch on my blog relying to this. http://blog.rauschgenerator.com/2009/09/26/using-callback-commands-instead-of-events/
#13 by jackson on September 25th, 2009 ·
I saw and commented about it. Thanks for doing the writeup. Between the three of us we have really explored the options an AS3 programmer has for doing callbacks!
#14 by Uwe Holland on September 26th, 2009 ·
Find my answers here. :)