By Scott Petersen
11 October 2010


Prerequisite knowledge

Familiarity with ActionScript 3 and its object-oriented features as well as familiarity with Flash application development.

User level


Required products

Sample files

Optimizing code is an important step in the development of just about any application. When it comes to devices such as the Apple iPhone, optimization is even more critical. Since the computing capacity of most mobile devices ranges from about 1/100 to 1/10 that of a modern desktop machine (depending on hardware particulars and the type of computation being performed), even applications that seem to just sip CPU resources on your desktop may run unacceptably slowly on a mobile device.
Memory consumption is a similar story. Not only does your desktop have substantially more physical memory than a typical mobile device, but it is capable of using virtual memory and paging to expand effective its usable memory to several orders of magnitude beyond what is available on a mobile device.
Finally, with reduced computing power, graphical rendering becomes more expensive. Adobe AIR can offload some of the workload to the GPU with OpenGL ES1.1 enabled. However, a fair amount of computation is often required just to keep the GPU busy, especially with a complex display list.
This article presents some general optimization tips as well as some strategies and techniques for dealing with the unique challenges of developing AIR content for iOS devices. You'll be able to see all of the points discussed here in action in the samples.

The importance of measurement

Before I get into particular strategies and techniques, one aspect of optimization that is often overlooked deserves a mention. Measurement is the key to effective optimization. There is nothing worse than spending a day optimizing a piece of code that you're sure is a bottleneck, only to find out that something totally unrelated was the real culprit. This is even more true of developing AIR content for iOS devices.
It is tempting to think of an iOS device as a slower version of a desktop machine. In reality, the performance characteristics are unique and can sometimes be counterintuitive. Moreover, on iOS devices, ActionScript in an AIR application is not executed by the familiar JIT compiler found in the Flash Player runtime on other platforms. It is compiled to a native iOS executable during development. This precompiled code has performance quirks different from those found in JITted ActionScript. In other words, your application's performance bottlenecks may be different when running on iOS devices versus the desktop.
At this point, the debugging and profiling feature set for iOS is a little light, but when all else fails, use trace().

Script performance

Many articles have been written about ActionScript performance tuning. The vast majority of general ActionScript optimization techniques will yield benefits on iOS devices. Following are some well-known techniques that are particularly suited to AIR content on iOS devices.
Use type annotations
var x; // wrong! var x:int; // right!
This technique is pretty much a given. It's not unheard of for code to see a five-fold performance increase simply by adding type annotations.
Use static member variables
private const myPI:Number = 2 * Math.acos(0); // wrong! private static const myPI:Number = 2 * Math.acos(0); // right!
When a variable is conceptually associated with a given class and therefore holds identical values for different instances of that class, it should be static. This has several advantages. Static variables are accessible from static functions. They only occupy memory once, not once for every instance of the class. Also, they only need to be initialized once. In the DiceSample app, the descriptions of the various configurations of dots for the various die faces aren't going to be different for different instances of the Die class; therefore, the variable is static:
// description of die faces private static const _dots:String
Use static member functions
Like variables, some functions are conceptually associated with classes and not with each instance of a given class. When this is the case, static functions should be used. Nonstatic member functions must be resolved against a particular object. When calling a static function, the exact function can be determined at compile time. This means that no CPU time needs to be spent determining the exact function to call at run time. The DiceSample app uses a static method to draw the various die faces in the Die class:
// create a sprite from each face private static function makeFaces(size:int):Vector.<Sprite>
Use final on classes
When a class is not destined for subclassing, it can be marked final. Marking a class final has some of the same performance benefits as static member functions. When a class is final, member functions can no longer be overridden. This allows compile-time determination of the exact function being called in cases when the class of a given object is known and is final. The TruchetSample app has a class TruchetTiles encapsulating a set of graphical tiles. This class is not subclassed and can therefore be marked final:
// Encapsulates a set of colored tiles // public final class TruchetTiles
Use final on methods
When it is not appropriate to mark an entire class final, but certain methods of the class are never overridden, those individual methods can be marked final. Like final classes, this allows the exact function being called to be determined at compile time. Unlike marking an entire class final, marking a method as final allows this compile-time determination for the marked methods only. The TileView class in the TruchetSample app is not marked final, as it may be useful to subclass it; however, its methods are marked final:
// set the origin of the view; xo and yo are in pixels public final function setOrigin(xo:int, yo:int):void
Use function objects instead of events
Event dispatch involves allocation of not just the Event object but various internal data structures to track the event. In many cases where an Event is often used, a Function object can do the same job with less overhead:
public function doSomething() : void { // ... m_evD.dispatchEvent(new Event("done")); } public function doSomethingBetter(whenDone : Function) : void { // ... whenDone(); }
Listen for an event once
When several handlers need to be aware of Flash events like MOUSE_DOWN or ENTER_FRAME, add one listener and have that single listener call several handler methods to inform multiple clients of the event. The Dice sample performs two operations for each event. Instead of listening to ENTER_FRAME twice, it listens once and uses FunMux, a simple function multiplexing class, to distribute the event to the two clients:
private var enterFrameMux:FunMux = new FunMux; // ... // only one listener for ENTER_FRAME so it only needs // to get dispatched once addEventListener(Event.ENTER_FRAME, enterFrameMux.invoke); // ... // animate dice each frame enterFrameMux.addFun(function():void { // ... animDiceMux.invoke(dur); }); // ... // z sort each frame enterFrameMux.addFun(function():void { zss.sortChildren(); });
Reuse objects
One of the most expensive parts of a Flash application is often memory management: that is, allocations and garbage collection. Reusing objects reduces both the amount of time spent allocating objects as well as the amount of time spent determining if objects are garbage that need to be collected:
public function doSomething() : void { var a : Array = new Array(); for (var i : Number = 0; i < aMillion; ++i) { a.length = 0; // Better than: a = new Array(); // ... } }
The Dice sample takes advantage of object reuse in calculating the Matrix3D in the Die class. In Die.updateMatrix(), the existing Matrix3D is "reset" using Matrix3D.identity() and reused rather than regenerated from whole cloth.

Rendering performance

While script optimization does yield benefits, you must also consider rendering performance if you want to optimize your iOS app to the fullest. Consider the following techniques, which aren't generally well known, since as they apply to AIR with GPU acceleration turned on.
Note: In the iPhone OS publish settings dialog box in Flash Professional CS5, there is a rendering selector that includes Auto, CPU, and GPU. When GPU is selected, some rendering operations will be GPU-accelerated—generally those that make use of bitmap operations. If CPU is selected, no GPU rendering will be used. Select CPU mode only if you encounter behavior, performance, or compatibility problems when running in GPU mode.
Use the GPU
With OpenGL ES 1.1 enabled, some of the drawing workload is moved from the CPU to the GPU. Maximizing the amount of work done by the GPU can radically improve the performance of an application. Following are some features that GPU hardware accelerates well.
Sample: 3D (DiceSample)
One of the most interesting features of the Adobe AIR rendering model is the intuitive 3D feature. 3D Sprites—that is, Sprites that have gone into "3D mode" as a result of having their transform.matrix3D, rotationX, rotationY, or Z properties set—have two characteristics that make them interesting from a performance perspective.
First, a rasterization of any given 3D Sprite is cached as a bitmap. This means that it is not re-rasterized every frame. Moreover, that cached bitmap is maintained across 2D and 3D transforms. So changing, say, the rotation or scale of a 3D Sprite does not result in a re-rasterization. Further, this cached bitmap will be maintained in video memory as a texture, minimizing bandwidth between the CPU and GPU.
Second, 3D Sprites use the GPU to do the final 3D transform and compositing. This frees the CPU from having to do all of the computation required to transform and render the Sprite with the 3D matrix applied.
The DiceSample app makes use of the AIR 3D feature to generate a simulation of dice bouncing around a virtual box. It performs well and it's simple due to the simplicity of the AIR 3D feature. Start to finish, it's under 500 lines of commented ActionScript code, with the core Die class being under 200.
Sample: Bitmaps (TruchetSample)
Bitmap objects are GPU-accelerated as well. Their image data is maintained in video memory as textures. For cacheAsBitmap and cacheAsBitmapMatix Sprite objects, the individual Sprite's cached image data becomes a texture. For Bitmap objects, the Bitmap's BitmapData is represented by a texture. This distinction is important. It means that if multiple Bitmap objects are on the display list, but they all have the same BitmapData object, they will only require one texture in video memory. As with 3D Sprites, final compositing of these objects is performed by the GPU.
The TruchetSample app uses a grid of Bitmap objects and a "map" of BitmapData objects to implement a simple, tile-based scrolling engine. The sample uses a tiling of 128 unique BitmapData objects to realize a 16000 × 16000 pixel (almost 67 square feet on a 163 ppi iPhone) image.
Sample: DisplayObject.cacheAsBitmapMatix (ShapesSample)
When building AIR applications for iOS, an additional DisplayObject property is available. The property goes by cacheAsBitmapMatix in the Labs release, but it is likely to undergo a name change before the API is finalized.
The cacheAsBitmapMatix property is conceptually similar to cacheAsBitmap; that is, setting this property to true on a DisplayObject ensures that a cached bitmap of the DisplayObject is retained across certain operations. For cacheAsBitmap, the cached bitmap is retained across translations (changes in x or y) only. The cacheAsBitmapMatix property retains a cached bitmap across changes in translation, rotation, scaling, and alpha. The cached bitmap is maintained in video memory as a texture. Rotation, scaling, and alpha transforms are performed by the GPU at composite time.
The ShapesSample app creates and animates a number of Sprite objects. By setting the cacheAsBitmapMatix property, the app uses the GPU to do most of the heavy lifting required to render each frame.

Where to go from here

You can get some additional tips, tricks, and under-the-hood information by watching the MAX 2009 conference session that I shared with Chris Brichford: Optimizing Flash content for iPhone applications.