Hack 100 Review Compiled ActionScript

figs/expert.gif figs/hack100.gif

Use Flasm to create low-level code optimizations or develop a general optimization-friendly coding style by examining compiled code.

A SWF decompiler allows you to recover uncompiled ActionScript [Hack #97] based on the bytecode in a SWF.

SWF decompilers are helpful when you want to recover (or steal!) the ActionScript source code, but sometimes you want to see the compiled bytecode, such as when you are optimizing your code for either speed or filesize. The open source Flasm utility (http://flasm.sourceforge.net) allows you to view bytecode compiled from ActionScript.

A different breed of tool from decompilers, Flasm is for the hard-core ActionScript guru who wants to optimize his code. It also satisfies general geek curiosity about what has become of your code by the time the Flash Player sees it. The Flasm documentation goes into how the Flash Virtual Machine within the Flash Player processes compiled ActionScript in some detail, which you might find both interesting and instructive.

The latest version of Flasm (Version 1.51) can handle Flash MX 2004 files. This is useful to compare how Flash Player 7 optimizes code versus Flash Player 6 (given that it is faster than previous versions) and helps you write code that lends itself to easy optimization. Flasm is a really useful application and not just a hacky curiosity.

Flasm

Flasm (FLash ASseMbler) is a free SWF compiler and decompiler initially written by Dave Hayden (http://www.opaque.net) and Damian Morton and currently developed/maintained by Igor Kogan.

It is a command-line-based tool, so you have to use either the command line (Windows) or an open terminal window (Mac). A Windows user interface called WinFlasm by Shariff Aina (the latest URL can be found in the Flasm docs or at http://flasm.sourceforge.net), which we use here for simplicity, avoids the command line altogether.

To install Flasm, simply download the ZIP file and expand it into a location of your choice. To install WinFlasm, expand its ZIP file and drop the application into the same location as Flasm. Mac users are (unfortunately) stuck with the command-line interface.

More detailed instructions for integrating Flasm into the Flash authoring environment, useful for developers with a keen interest in creating highly optimized code, are available in the Flasm docs.

After compiling your SWF file using the Flash authoring tool, run WinFlasm and select Open to browse for your SWF.

Specify what you want to do with the SWF using the radio buttons in WinFlasm's lower pane, as shown in Figure 12-6.

Figure 12-6. The WinFlasm interface for Flasm
figs/flhk_1206.gif


I usually choose Decompile and Edit Directly, which, despite its name, doesn't decompile the SWF so much as display the compiled SWF. Click Execute to open the compiled SWF in WordPad, as shown in Figure 12-7.

Figure 12-7. A compiled ActionScript file shown in WordPad
figs/flhk_1207.gif


Using the Compiled Listing

The compiled listing is your best indication of the final code that the Flash Player sees at runtime. The ActionScript in your FLA is converted to a stream of bytes called bytecode, the purpose of which is to compress the code by converting actions and methods into shorter byte-based tokens and to convert more complex actions and methods into the longer series of simpler bytecodes that the Flash Player actually understands.

Anyone who has had experience with assembly language will see that the internals of the Flash Player Virtual Machine are very much like the old 8-bit microprocessor architectures. It includes a stack onto which values to be manipulated (added, subtracted, etc.) are placed (pushed ) before being processed. The results are then taken from the stack (popped ). Hence, the Flash Player is stack-based rather than using absolute memory locations. To store intermediate values, registers are used, and these speed up the process significantly for long or repetitive calculations. The alternative to registers appears to be constants, where "constant" probably refers to a constant location.

Working with Flasm

Let's look at how Flasm can help you create efficient modular code. The other day, I wanted to see the effect on overall performance of using functions. Although functions make for more modular and structured code, I wasn't too sure about using function calls in performance-critical areas because common sense told me the function invocation itself would take extra time. Anyway, I thought it would be a good idea to investigate it in Flasm.

Here is the code using no functions:

var x = 5;

var y = 6;

var z = x*y;

trace(z);

for which Flasm yields the following Flash Player 7 bytecode:

frame 0

  constants 'x', 'y', 'z'  

  push 'x', 5

  varEquals

  push 'y', 6

  varEquals

  push 'z', 'x'

  getVariable

  push 'y'

  getVariable

  multiply

  varEquals

  push 'z'

  getVariable

  trace

end // of frame 0

If I use ActionScript 2.0 datatyping:

var x:Number = 5;

var y:Number = 6;

var z:Number = x*y;

trace(z);

I get exactly the same output, telling me that there is no performance or filesize hit caused by using typed code (as would be expected because type checking is performed at compile time and not runtime). On my machine, both typed and untyped versions execute in approximately 0.06 ms (excluding the trace( ) command because output statements tend to be slow). I timed the execution using code of the following form (we test for 1000 iterations to get an average speed):

// Time how long a for  loop itself takes

startTime = getTimer( );

for (var i = 0; i < 1000; i++) {}

endTime = getTimer( );

measureTime = endTime - startTime;



// Run some simple code, and trace how long it takes

startTime = getTimer( );

for (var i = 0; i < 1000; i++) {

  // Put code under test here 

}

endTime = getTimer( );

codeTime = endTime - startTime - measureTime;

trace(codeTime/1000);

Here is a version that uses a function to perform the earlier operation:

function multiply( ) {

  return x * y;

}

var x:Number = 5;

var y:Number = 6;

var z:Number = multiply( );

trace(z);

Again, Flasm yields the following bytecode version. As expected, it is longer in terms of bytecode and elapsed time (0.16 ms, more than twice as long as the non-function-based version). Not good.

frame 0

  constants 'x', 'y', 'z', 'multiply'  

  function multiply ( )

    push 'x'

    getVariable

    push 'y'

    getVariable

    multiply

    return

  end // of function multiply



  push 'x', 5

  varEquals

  push 'y', 6

  varEquals

  push 'z', 0.0, 'multiply'

  callFunction

  varEquals

  push 'z'

  getVariable

  trace

end // of frame 0

I then tried it with a function invocation with arguments, thinking that this would create the longest bytecode of all:

function multiply(a, b) {

  return a * b;

}

var x:Number = 5;

var y:Number = 6;

var z:Number = multiply(x, y);

trace(z);

But this version runs marginally faster (0.12 ms)! We can see why when we look at the Flasm bytecode. Using function arguments generates bytecode that accesses the arguments directly rather than having to look for the variables holding the values. This results in shorter bytecode and faster execution.

frame 0

  constants 'x', 'y', 'z', 'multiply'  

  function2 multiply (r:2='a', r:1='b') ( )

    push r:a, r:b

    multiply

    return

  end // of function multiply



  push 'x', 5

  varEquals

  push 'y', 6

  varEquals

  push 'z', 'y'

  getVariable

  push 'x'

  getVariable

  push 2, 'multiply'

  callFunction

  varEquals

  push 'z'

  getVariable

  trace

end // of frame 0

If you write your code using functions that pass values via arguments, the Flash Player makes greater use of direct registers rather than memory locations (which have to be pushed into registers or onto the stack before they can be used, doubling access time). This is a very interesting finding because it implies that Macromedia is aiming ActionScript toward a more structured coding style that tends to use functions, references, and parameters rather than separate variables.

Experimentation shows that the time required to invoke functions is made up for as you place more code within each function; writing clear, modular code actually increases performance in many applications.

Thus, the Flash Player 7 optimizations that Macromedia advertises are actually most seen when you use the sort of OOP coding style that ActionScript 2.0 promotes.

If you try our examples here with Flash Player 6 (Flash MX) you will see a different result, unless you use Flash Player 6r65 (check the Optimize for Flash Player 6r65 in FilePublish Settings Flash tab), which seems to act very much like Flash Player 7 (probably because it fundamentally is Flash Player 7, apart from the new ActionScript classes added in Flash MX 2004). Looking at previous versions of the Flash Player using Flasm, it becomes obvious that the Flash 7 and Flash Player 6r65 code optimizations are strongly linked to the way that your bytecode makes efficient use of registers and the stack.

But you have to write code that takes advantage of the optimizations, such as by:

  • Passing arguments to functions

  • Using local variables

  • Passing the target of an animation routine as an argument rather than using the this property

Another set of tests you may want to make is the differences between ActionScript 2.0 class declarations and ActionScript 1.0-style timeline-based code or prototype-based inheritance.

Hint: ActionScript 2.0's strict datatyping comes at compile time (so there's no performance cost) and class-based OOP uses well-defined functions and local data (both of which lead to code optimizations in the bytecode through use of registers rather than memory). This means that class-based ActionScript 2.0 code produces highly optimized bytecode, as does any structured coding style that uses functions and localization of data within functions and arguments.

Final Thoughts

Flasm is very useful to investigate what becomes of your ActionScript in the final SWF. Although it may initially seem a little difficult to understand, you can easily get a feel for it if you start with short, simple scripts as we have done in the preceding examples.

Flasm is also useful for checking for any ActionScript bottlenecks. Be aware that for motion graphics applications, the performance bottlenecks are caused by Flash's graphic-rendering engine [Hack #68] and not bytecode.

This hack has, of course, only scratched the surface of Flasm and code optimization. If you are an advanced ActionScripter looking for new avenues with which to hone your game, digging into Flasm to see what you can learn about the Flash Player environment will turn up more than a few gems that enable you to squeeze a little more performance out of your SWFs.