Events vs. Callbacks

Before there were events, there were callbacks.

I remember finally grasping callbacks… it gave me a perspective on my code where different components could have an asynchronous conversation — you know, just like we do every day with email (and any other form of messaging).

Finally getting events, and in particular AS3′s event model, was a similar revelation. Components could be oblivious to each other, or even how far away they were from each other in the giant graph of your application, and still carryout a conversation of signaling and acknowledging various messages.

Of course, this isn’t new to anyone who learned to code in SmallTalk — which seems to be the language that nailed every good thing about OOP without tripping over any of the bad things. No surprise, it came out XEROX PARC. Damn, did those guys invent all of modern computing like 30 years ago?

Naturally, when we built Mockingbird we used events to notify the view of changes in the model. Very standard MVC stuff, folks have been doing it for decades. And like everyone doing it with AS3, we used the Flash Player’s native EventDispatcher class. In fact, since we were using the Flex Framework, we even used the [Bindable] tag to do all of the work for us.

Well, everything has a catch. And even though EventDispatcher is a native part of the Flash Player and an integral aspect of AS3 coding, it still has overhead that you may not need and can cut out by crafting your own solution.

Up front, let me say that premature optimization is the root of all evil. And by evil, I mean you’ll waste your time on it with no apparent benefit. The following information is really only useful if you’re dispatching tens of thousands of events per second. You’re probably not, or at least not very often. We were, but Mockingbird is a special case.

One thing bugged me about using EventDispatcher: it made a copy of my event every time I called dispatchEvent(). Now, my pesky lizard brain that I honed while working on the PS2 taught me that lots of little memory allocations every frame — in a garbage collected environment — is not the ideal for steady, consistent performance.

If EventDispatcher was a standard class we could hopefully just subclass it and try to plug in a pooled memory allocator or similar. But it’s native, so we can’t do that.

So, our only choice is to create our own event dispatching mechanism.

It’s actually not that hard, particularly if you don’t need features like bubbling or propagation control. We didn’t even need priorities as it was purely notification of change, usually to just one or two listeners. I ended up with a couple of methods for adding and removing functions to an array and a dispatch method that simply iterated through the array calling each function in turn (callbacks!).

And boy, did it work. Zero memory allocations. Even simplified the API compared to IEventDipsatcher. Here’s a quick performance test I built to show the benefits in a theoretical context.

Source | SWF

It runs 100,000 iterations on each test, and each iteration “dispatches” an Event object that has 3 registered listeners. I don’t time registrations, just the dispatch. The listeners are actually do-nothing functions, but since mxmlc doesn’t dead-strip empty functions the test should be representative.

On my machine, comparing dispatchEvent vs. a Function callback is 1275ms vs. 80ms. That means the callbacks are 15.9x faster. I didn’t test the memory usage, but there would be a significant difference there as well, which means eventually you’ll have to pay the garbage collector his timeslice, compounding the overall performance cost for your application.

Damn, I was proud of myself. I just found a 15.9x performance gain in an integral part of our technology that’s been a hotspot on the profiler since forever. So, I went and swapped all of our event dispatch code for callbacks, tweeted several times about my success, and patted myself on the back.

Then I started writing this blog post. And in the process of consolidating my thoughts and cleaning up my source code I had an idea.

What if EventDispatcher didn’t do any memory allocations? How much faster could it run?

You’re probably asking yourself, didn’t we already go over this a few paragraphs up? You can’t hook EventDispatcher‘s allocator. Or can we?

How does EventDispatcher allocate new instances of your custom events? It calls the clone() method, which you’re supposed to override if you need those clones to be typed as your custom event. The default clone() returns a new Event instance. How about a clone() that didn’t allocate any memory? What if we returned this?

Source | SWF

This performance test compares dispatchEvent with custom events that don’t allocate memory when cloned vs. Function callbacks. You can view it here.

Results? 7 ms vs. 83 ms That’s right, when you take the new out of dispatchEvent() it becomes 182x faster… 11.8x faster than a callback!

How fast is it? Well, the theoretical maximum speed for calling functions can be determined by inline the callbacks (instead of using an array of Functions, which have closure overhead). This runs around 2ms.

What does this mean? Well, it means that I need to go back and revert some of my commits from this afternoon. It also means you should check out your profiler and see if event dispatch is a hotspot for you, and if it is, try subbing in custom events that don’t allocate.

What do we lose by not allocating? No idea. I’ve not done that testing yet. Some suggested (based on the docs, I believe), that there’s something with EventDispatcher not being able to change the target property after an event has been dispatched, so it has to create a clone if you re-dispatch the event, but that just doesn’t make sense — I can’t think of an implementation that would require that or benefit from it. It could affect bubbling, not sure how though.

In the end, it may just be a safety measure to ensure that events can’t tamper with the original event before other events get a chance to consume it. Kinda like cloning a private array before returning it through a public method (for example, in collection classes). I’d be curious to know.

NOTE: My timings were done in the standalone Flash Player. Testing out this post I’m noticing that the numbers aren’t quite the same in the browser (not surprising).

  • http://coderhump.com/ Ben Garney

    Nice post, Troy. Great to see hard numbers on this stuff.

  • http://samueltoth.com samuvagyok

    Good article, thanks for posting

  • http://www.industrybroadcast.com Ryan Wiancko

    Nice work! Great to see programming articles without code samples cause that means I can read em aloud on Industrybroadcast.com , which I’ll be doing shortly good sir!

  • http://lukesh.com Francis Lukesh

    Great post! Perhaps a reason that events clone is to facilitate garbage collection? If you don’t clone an event and it has a few listeners, I can see a scenario where objects might hold on to the event and not release objects that you would expect to be released. What do you think?

  • http://www.chrisrebstock.com Chris Rebstock

    Interesting stuff you’re digging into here. I’m also curious why clone creates an entirely new instance, and I think Francis may be on to something. Especially in the case of bubbling, where the event will hit each listener for every object all the way up the chain. I imagine if you do not clone the event, it cannot be released from memory until all those objects are deallocated?

  • http://www.xtyler.com Tyler Wright

    Ack! Yes this would be a perfect solution, except Flash Player screws it up for us. If you look at the behavior of your solution (not just the stats) you’ll see that all subsequent dispatchEvent calls fail when using a non-cloning Event. I’ve tried this same solution – whatever Flash is doing under the hood, if it receives an Event that has already been used it aborts the dispatch cycle, which is why you’re going to fast!

    I’ve even tried overriding the target and currentTarget setters and everything to try and make the Event object appear unused, but I still haven’t gotten it to work.

    Sorry man, please contact me if you find a solution using EventDispatcher. Actually, everything in Flash types to IEventDispatcher so it might not be a bad solution to completely replace the built-in EventDispatcher, even to the extent of overriding your Sprite’s ED methods.

    Good luck!

  • http://troygilbert.com/2009/09/events-vs-callbacks-revisited/ Events vs. Callbacks (revisited) – Troy Gilbert

    [...] Events vs. Callbacks [...]

  • http://www.jacksondunstan.com Jackson Dunstan

    Thanks for the excellent post. I too have found callbacks to be much faster than events, but didn’t know that the clone() method could be overridden to eliminate an allocation. I’m quite interested to find out what the ramifications of that are. If they are minor, this technique could be a big win in some areas of my code. Thanks again, -Jackson.

  • http://blog.rauschgenerator.com/2009/09/26/using-callback-commands-instead-of-events/ Using Callback Commands instead of Events | RGB(log)

    [...] I’ve read posts about the speed advantages of callbacks over events on the blogs of Troy Gilbert and Jackson Dustan. But instead of using a strategy like stated in the comment here we use [...]

  • http://corp.hive7.com/2009/11/16/ditch-your-events-part-1/ Hive7 » Ditch Your Events (Part 1)

    [...] them over to AS3. Let’s start at the core. In order for things to perform their best, I couldn’t use the built-in Events. Though Troy did the benchmarking legwork, he didn’t provide an [...]

  • http://www.uksal.com/blog Ozgur Uksal

    Hi,
    Calling each function (callbacks) rather than calling dispatchEvent() clearly improves the performance. It is because the application won’t need to create an Event type object in the memory but will keep the address of the last function in the stack. Creating an object in the memory is a very expensive operation for the OS.

    However, the design of your code won’t be in good shape. If there are other developers working with you, then it will be hard to understand the logic flow of your application.

    Robert Penner has a new event model: http://robertpenner.com/flashblog/2009/09/as3-signals-getting-stronger.html
    I doubt if it offers any performace gain. On the other hand, it improves the readibility of your source code.

  • http://letero-pizza.ru/ Letero

    Вы устали от некачественного товара, которым забиты полки магазинов, и хотите чувствовать себя на рынке как рыба в воде? Посетите наш web-сайт, где есть множество статей, прочитав которые вы узнаете мнение профессионалов практически о любом товаре современного рынка.

  • Zarutian

    If EventDispatcher is invoking clone() on the events it dispatches to prevent mutation of it between event handlers then why isnt the event just made immutable?

  • http://www.francescomalatesta.net/ Francesco Malatesta

    that's an awesome post!

    could I translate it on my blog? (that's an Italian blog about flash, game programming in flash and so on)

    bye!

  • troygilbert

    Go ahead, feel free to translate and post. Please just include a link back to this article (or to my homepage, http://troygilbert.com/). Thanks!

  • http://www.francescomalatesta.net/ Francesco Malatesta

    that's an awesome post!

    could I translate it on my blog? (that's an Italian blog about flash, game programming in flash and so on)

    bye!

  • troygilbert

    Go ahead, feel free to translate and post. Please just include a link back to this article (or to my homepage, http://troygilbert.com/). Thanks!

  • http://me2day.net/rinn/2010/04/30#13:12:10 rinn’s me2DAY

    퍼플린의 생각…

    Event vs CallBacks – Troy Gilbert 관련 포스트를 블로그에 하나 쓸까 했으나.. 아직 언제 시간이 될지 모르것군 -ㅅ-;;;;…

  • Amy

    Did you verify that when you override the clone() method to return this that the “listener” function actually gets called? My experiments say it doesn’t (http://insideria.com/2009/12/can-event-performance-be-impro.html), which would certainly explain your increased performance when you do that :).

  • Anonymous
  • Csj4032

    Thanks

  • L_B

    The problem come of the name you use for your event : “change” (Event.CHANGE).
    If you use Event.CLEAR for exemple or a custom string you will not have this problem.
    If you use “change”, you’ll have a lot of dispatch of your event and therefore a lot of duplication i think.

  • http://troygilbert.com/ Troy Gilbert

    The choice of event name has zero impact on the discussion above.

  • L_B

    The choice of your event “change” (Event.CHANGE) impact your first performance test.
    Replace “change” by “Change” for example and you will see the difference.

  • L_B

    My mistake, i was too fast…sorry for the flood (you can delete our conversation below)

  • Hays Clark

    Interesting, this makes as lot of sense, but I think that there’s another detail which is masking the truth. ( I might be wrong, but bare with me for a sec. )

    Your ‘Callback’ system is faster then the native Event system in tests, but this doesn’t make sense because the Event system already has the upper hand. The Event system is native ‘Machine Code’ already because it’s compiled into the Player and should run as fast as C code, vs your callback system is ‘Interpreted Code’ and can’t run as fast as the built in player functionality. So something else is making the Event code run SLOWER then your ‘Interpreted Code’. I believe the Flash Players JIT is causing this slowdown. Here is a little article about the JIT in Flash. The key part is that Constructors remain in ‘ActionScript Byte Code’ while everything else is converted to ‘Machine Code’. It’s my guess that because the Event systems heavy uses Constructors, that is the reason why the Event system is slow. By elimination the cloning it’s optimizing the slow construction of objects in Flash, however, you might also be able to speed up the Event system without having to kill the clone functionality. A good test might be to optimize the constructors by only using and init() method and see if your performance speeds up.

    http://blog.classsoftware.com/index.cfm/2010/11/4/Heavy-constructor-JIT-optimisation-in-ActionScript#c33099700-D56E-59C1-07EACFF9ED89BF3A

    -Hays