AlecMcE.com

Coding on the Flash Platform

Replacing ENTER_FRAME – Testing Tickers

View Comments

Last week I posted a reflection on a discussion about strategies for replacing ENTER_FRAME listeners. The ActionScript 3 event model requires an event object to be constructed every time an event is dispatched. Constructing objects is slow, which doesn’t matter in many contexts, but in speed-critical contexts, we would like to minimise the amount of time we spend dispatching events. The article considered a way to do this.

This article discusses a first attempt to test the relative performance of two ’solutions’ to the problem of optimising a game loop ‘ticker’, and is based on this source code:

http://github.com/alecmce/TickerStrategies/

Two Approaches To The Problem

If you have already read the previous article, you might want to skip this section.

From the discussion, it appears that most developers who’ve run into this problem have created a ‘ticker’, which creates one ENTER_FRAME listener:

stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);

Robert Penner introduced a second idea which uses a two-frame MovieClip and exploits the undocumented addFrameScript method to attach methods to the timeline frames. Both do the same thing: each frame they iterate over an array of methods and call them:

addFrameScript(0, onEnterFrame, 1, onEnterFrame);

This idea stems from an hypothesis that because the scripts are called from a MovieClip timeline, they do not have an associated event object. There should therefore be a small but potentially significant time-saving in using the frame script approach.

Compiling For The FrameLoop Approach

The two-frame loop is required because if you try to ‘play’ a one-frame timeline, Flash is smart enough to recognise that it’s a bit pointless, and will do nothing. If you play a two-frame timeline, it will loop continuously.

One solution is to create a symbol in an FLA with two empty frames, but I rarely if ever touch the Flash IDE as a developer, and would prefer an elegant solution which does not require the IDE.

My first thought was to use the [Frame(factoryClass=etc)] metadata structure, but this turns out to be problematic. I could build without problems and what I built ran within certain player contexts, but not others.

The metadata structure is unsatisfactory, because it required creating two classes that act as ‘main classes’ – it looked messy. Happily, I have since found (via senocular.com’s documentation of the MXML compiler) a rather pleasing way of creating a 2-frame (or more) MovieClip through MXMLC. By adding the following line to the end of the compiler arguments, I force the SWF to be built with a second frame (frame 1, zero-based indices):

-frames.frame 1 flash.display.Sprite

Testing The Hypothesis

My implementations of the ‘ticker’ can be found in the tests that I have created can be found at http://github.com/alecmce/TickerStrategies/. Comments, critiques and suggestions would be gratefully received.

The solution therefore was to create MovieClips with minimal structure built on top of them that would put the player under stress, and to implement both strategies. They could then be run side-by-side and evaluated. Here are the test builds. They run only if you mouse-over them.

EnterFrame Ticker

The Flash plugin is required to view this object.

FrameLoop Ticker

The Flash plugin is required to view this object.

Notes on the Tests

The profiling tool at the top is a hacked version of the Lost In Actionscript SWFProfiler tool. I modified the styles and exposed more of the API by making some minor changes to his original. My variation can be found here.

Unlike the original idea of replacing an event listener with a signal from #as3signals, the frame loop implementation I created is as pared down as possible. This is in order to make the test as fair as possible.

As far as I can interpret the tests so far, there is almost no performance difference between the two, but there might be a small memory gain by using the frame loop. Every time I run the tests I get different results however, and a thorough record of results in order to find their normal distribution is probably needed.

The test probably need to be more rigorous. They currently test one particular point of heavy stress for the player, but there may be circumstances in which one or the other strategy starts more obviously out-performing the other. If you have an idea about how to improve these tests, please post it.

I did consider using Grant Skinner’s Performance Harness but dismissed it in this circumstance. Test harnesses perform tests on synchronous code very well, but it is not clear to me how you could test this sort of performance on a similar basis. Rather than testing milliseconds for execution, this test is trying to evaluate the memory overhead of events object construction and destruction and any potential performance implications of that and the different mechanisms for firing methods on entering a frame.

Written by alec

November 29th, 2009 at 11:34 pm

Posted in as3, opinion

  • Hi Alec,

    I just forked your repo and added another test case where I use a Timer object that ticks every 1000/defaultFrameRate milliseconds (to emulate the enter frame speed) and I didn't notice a big difference between the three approaches.

    here is the repo
    http://github.com/vizio360/TickerStrategies
  • I also wonder if the graphic intensity is throwing off the test results - my CPUs get maxxed out, so perhaps any gain from the frame loop is being negated by the elastic racetrack.
  • Thanks for the feedback, Shaun.

    Yes, I wondered whether the weight of the test might be an issue, but I wanted to ensure that it would run at slower-than-31fps in order to test performance. If I don't find the point at which the racetrack comes under strain I'm not sure how to differentiate the performances. However, perhaps I shouldn't put it under so much strain. Do you have a suggestion? I will try with 2,500 sprites and see how they run.
  • jacksondunstan
    I think the different results you're seeing may be a result of the heavy use of Math.random(). This is, by nature, introducing a random element into your tests and making them unrepeatable and unverifiable. You might want to try a repeatable random seeded with a constant value in both tests. This allows you to process (virtually) unpredictable numbers and still have some repeatability. Frankly though, I'm not sure why random numbers would be necessary at all. This becomes clearer if you test out other performance paths (like you suggested above), such as those with no graphical elements at all.

    Thanks for these articles, tests, and the open code. I'm interested to see if all this discussion leads to any measurable performance increase. If it did, I think a lot of AS3 developers would switch their main ENTER_FRAME loops. I know I sure would!
  • As ever Jackson, thank you for your comment! I am extremely grateful that you read my work carefully and point out contentious points.

    When I mentioned that I get "different results" I was really referring to the different results I get on different platforms, different players, and so on. The gist of the results (that the Frame Loop appears to use marginally less memory but doesn't appear to have a performance benefit) seems consistent. However I want to be scientific about it so put in the proviso that I do get inconsistent results and I need to be more rigorous.

    The random number generation issue is an interesting one. Your repeatable random seed generator is terrific for the sorts of scenarios that you offer such as gaming where you need to ensure that each time you repeat a scenario 'random events' happen in a consistent way. In this case, I can guarantee that events happen in roughly the same sort of way (and that the 'roughness of that is actually pretty smooth) by sheer weight of randomness. There are 5,000 sprites, to which a random acceleration vector is applied (2 random numbers) every frame. That's 300,000 random numbers being applied to the model every frame. Over the course of the animation, the aggregate velocities of the elements will approach a normal distribution around zero-velocity. (I'm going to let that point dangle and point to http://en.wikipedia.org/wiki/Central_limit_theorem).

    As you say however, I could implement your (or another - as well as Mersenne Twister, also consider http://en.wikipedia.org/wiki/Rule_30) repeatable random seed by creating a random seed in JavaScript and injecting that seed into the two Movies to guarantee that they run in parallel each time a user runs them. It would be a pleasing addition to the test, though I very-much doubt it would change the results. I will add it, if I get the time.
  • jacksondunstan
    Thanks for the clarification. I assumed that what you were seeing was roughly what I was seeing: inconsistent framerates on the same computer, browser, and player. On my 2.2 Ghz Intel Core 2 Duo with 2 GB RAM on Mac OS X 10.6 (Firefox 3.5) I was getting anywhere between 18 and 20 average FPS on both tests. I would let one of them run for 30 seconds, stop it, run the other for 30 seconds, stop it, and repeat. Sometimes the average would be 18, sometimes 20, and sometimes in between them. Neither test seemed more likely than the other to perform slowly or quickly. I even refreshed the page and tried it a few more times, but there was no change. From this it seemed likely that the random number generation was causing essentially random results, even if only slightly differentiating the two tests. Hence my suggestion to use a random number generator with a constant seed. I don't think any JavaScript injection would be necessary since you just need to make sure the seed is the same between them. From there it's essentially random and repeatable between both tests.

    Since you pointed out differences between platforms and players, I just tested again on my 3.0 Ghz Intel Core 2 Duo with 4 GB RAM running on Windows XP (Internet Explorer 8). Here I get much closer results than on the Mac. Both seem to run at 17.8 FPS and only vary by +- 0.01 FPS. I should note that I never see any "spikes" in either performance or memory usage on any platform with any test. At least this shows that the FrameLoop ticker is no worse than the ENTER_FRAME ticker...
blog comments powered by Disqus