01 July 2010

Myth Buster: EnterFrame Event Performance

When dealing with animation and tweening in ActionScript, EnterFrame event is the most important factor. However, it is also one of the frequent factors to cause performance issue for your application. Using it efficiently is critical for every application.

The myths

The EnterFrame events reportedly generate 1500 – 1800 event objects per minutes. Imagine, in every instance of your component (for e.g. a text marquee), you add an EnterFrame handler to its own, that number of objects generated per minute will multiply. Keep in mind that, creating an object is one of the slow actions in ActionScript. So avoid creating too many event objects will theoretically improve application performance.

When discussing AS3Signals, I have mentioned an approach (raised by Robert Penner and AlecMcE) which uses a two-frame MovieClip which has some script attached to its frames. The MovieClip loops continuously between 2 frames. When the play head reach a frame, the script attached to it will call the handling function, imitating EnterFrame’s events. Some claims that: “The idea behind the approach is that a frame-loop is an internal Flash player structure. It sits underneath the AS3 functionality, and so it doesn’t produce ENTER_FRAME events”.

The experiments

demo

To clear the myths about EnterFrame event performance and the 2-frame-loop approach, I have constructed an experiment of moving 5000 objects simultaneously using 4 different approaches:

1. A singleton frame-loop Manager. (View demo)

This singleton manager will extends a 2-frame movieclip. This movie clip is created before-hand in Flash CS. Attach the same enterFrameHandler to its 2 frames. The moving balls will attach their enterFrameHandler to this manager via Signals:

  1. public class TwoFrameManager extends TwoFrameMovie {
  2.     static private var _instance: TwoFrameManager = new TwoFrameManager();
  3.     public var enterFrame: Signal;
  4.     public function TwoFrameManager() {
  5.         enterFrame = new Signal();
  6.         addFrameScript(0, enterFrameHandler, 1, enterFrameHandler);
  7.     }
  8.     private function enterFrameHandler(): void {
  9.         enterFrame.dispatch();
  10.     }
  11.     static public function get instance(): TwoFrameManager { return _instance; }
  12. }

 

2. A singleton EnterFrame Manager. (View demo)

This manager will work the same as previous one except it extends a Shape (the most simple display object that can be created with ‘new‘ statement) and add an event listener to its own EnterFrame event.

  1. public class EnterFrameManager extends Shape {
  2.     static private var _instance: EnterFrameManager = new EnterFrameManager();
  3.     public var enterFrame: Signal;
  4.     public function EnterFrameManager() {
  5.         enterFrame = new Signal();
  6.         addEventListener(Event.ENTER_FRAME, enterFrameHandler);
  7.     }
  8.     private function enterFrameHandler(event: Event = null): void {
  9.         enterFrame.dispatch();
  10.     }
  11.     static public function get instance(): EnterFrameManager { return _instance; }
  12. }

 

3. Multiple frame-loop instances (View demo)

Every moving ball instance will attach its enterFrameHandler to the frames of a new TwoFrameMovie. i.e. there is one TwoFrameMovie for each moving ball.

  1. public class MovingBall extends Sprite {
  2.     public var enterFrame: TwoFrameMovie;
  3.     public function MovingBall() {
  4.         enterFrame = new TwoFrameMovie();
  5.         enterFrame.addFrameScript(0, enterFrameHandler, 1, enterFrameHandler)
  6.     }
  7.     /*...*/
  8. }

 

4. Multiple EnterFrame instances (View demo)

Every moving ball will add an EnterFrame event listener to its own instance:

  1. public class MovingBall extends Sprite {
  2.     public var enterFrame: TwoFrameMovie;
  3.     public function MovingBall() {
  4.         addEventListener(Event.ENTER_FRAME, enterFrameHandler);
  5.     }
  6.     /*...*/
  7. }

 

Results:

Running the demos a few time, I have come up with these average statistics (reported through the same Stats panel):

My computer: Intel Core 2 Duo 2.53Ghz, 2GB RAM, Win XP, Firefox 3.6, Flash Player 10.0 debugger.
Moving 5000 objects after 70 seconds. Compiled frame rate is 30.

  1. Singleton frame-loop manager:
    - Average FPS: 23-24
    - Max Memory: ~ 20.900 MB
  2. Singleton EnterFrame manager:
    - Average FPS: 24
    - Max Memory: ~ 20.900 MB
  3. Multi-instances, frame-loop movie:
    - Average FPS: 18
    - Max Memory: ~ 24.300 MB
  4. Multi-instances, EnterFrame event :
    - Average FPS: 13
    - Max Memory: ~ 24.000 MB

The source code of my demo is placed here.

What is learned from those numbers?

  1. First, with a single event listener or frame-loop, there’s virtually no difference in performance and memory.
  2. Multi instances of frame-loops or enterframe handlers will both significantly reduce our application performance.
  3. The fact that frame rate of demo 3 higher than demo 4 can be considered a proof that frame-loop doesn’t generate objects (hence it’s performance is better).
  4. Although faster, demo 3 consumes more memory due to the fact that it must create a 2-frame MovieClip for its every instance. (I haven’t tried to improved this anyway)
  5. Creating many objects is really an ‘evil’ thing to application performance.

Conclusion

The Myth Buster way:

  • Many event listeners (EnterFrame in particular) will reduce performance: Confirmed
  • Frame-loop doesn’t create EnterFrame objects: Feasible

My way:

  • It is strongly recommended that you should use an EnterFrameManager or TickManager (depending on how you call it) for every project needs EnterFrame events. The demo is using Signals to manage handlers. You can implement a non-Signals Manager with ease.
  • It’s possible to conclude that frame-loop is more efficient than EnterFrame events. However, the fact that 2 singleton approaches don’t have significant difference in performance, you can safely use the traditional (singleton) event approach for the sake of simplicity.

[Vietnamese tag: giải mã những giả thuyết về vấn đề tối ưu tốc độ của sự kiện EnterFrame]

7 comments:

  1. Wow, awesome benchmarking!
    Thank you!

    ReplyDelete
  2. Good tip! I have already implement TickManager for Restaurant City project. Besides, I add one more parameter into the tick function

    public function tick(time: int) {
    }

    The time is used to calculate the elapsed time between enter_frame. This will help us to update the animation more properly regardless of current frame-rate.

    ReplyDelete
  3. I just ran all 4 demos and am not seeing any difference... FPS for all 4 are around 30-31, and RAM on all 4 are around 18MB. I'm running FP10 in Safari 5.0.3 on a 2nd generation iMac 27", 2.93GHz i7 processor w. 8 GB RAM, OSX 10.6.6

    ReplyDelete
  4. In my tests, I have set the number of particles large enough so that all demos run under default frame rate (at the power of my machine) to clearly see the differences. The lower the frame rate is, the slower the calculation. However, your machine is so fast that all demos run at its default frame rate so it is not possible to observe the differences then (Have you check the CPU usage for which consume more?). I should have allowed to change the number of particles for faster machines like yours.

    About the memory, I cannot explain but maybe you are using FP 10.1 which has better event objects handling or the like. Some of my conclusions should be revised and may not be correct for later version of FP.

    ReplyDelete
  5. a year later ... has anyone tried these tests on AIR 2.7 running on Android/iOS?

    ReplyDelete
  6. I just read it today..thanks mate...you saved me from concluding this as a 'bug' hehe.

    ReplyDelete
  7. Dude, you saved me! It's really nice to see post like this. Very useful

    ReplyDelete