Nov 29, 2012

20 Tips To Optimize Your Actionscript

This article was written several years ago for Experts Exchange, and has proven to be a little more controversial than I expected or intended, mostly due to the (admittedly vague) reference to what I call AS3′s “fake” lazy evaluation, which happened to be (rather randomly) listed as the first tip. I’m removing that tip from the list, not only because it’s likely confusing, or that the performance gains realized by following it would be minimal at best, but also because it might be a safe assumption that future version of the player might correct it. First, a quick word about the issue itself.

AS3 (and many languages) use “lazy” evaluation. That’s to say that a boolean expression will stop evaluating as soon as it can. Take the following expression:

1 if ( a > b || c > d)

That means that the second condition (c > d) won’t ever be examined at all, if a is greater than b. This is usually not terribly noticeable, since it’s not obvious if the second component was evaluated. To increase visibility, we can use a function that traces output to the console, as well as returning an value to be evaluated.

1  function isTrue(){
2     trace('isTrue is evaluated');
3    return true;
4 }
5  if(isTrue() || isTrue()) {};

You’ll notice that it only traces once. Since it return true the first time, the second invocation never happened. This is where things get sticky. For the most part, AS3 behaves lazily. If you were to create a very slow function (e.g., perform 100000 string concatenations), and put it in a conditional where it should never be evaluated, you’ll see that it’s not.
1 if(true || veryHeavyFunction())
The above would behave as expected – lets say veryHeavyFunction normally takes 10ms to execute – even in a loop of 10,000 iterations, you’ll still get back a very low number.
However…
If it was truly lazy, then this:

1 if(false || true || true || true || true || true || true || true || true || true || true || true)

Should evaluate just as fast as this:

1 if(false || true)


Both should stop evaluating as soon as the first “true” is encountered, and in fact it in most respects it appears to. But that’s where the waters muddy… For very large iteration sets, the former runs about 7 times slower than the latter in the tests I ran on my personal machine several years ago. Why is this? No idea. This concept seemed to irritate a lot of people. It irritates me. But that’s what happened (in my own empyrical testing, anyway)

On to the article…

Introduction
This article is primarily concerned with ActionScript 3 and generally specific to AVM2. Most suggestions would apply to ActionScript 2 as well, and I’ve noted those tips that differ between AS2 and AS3.

With the advent of ActionScript 3.0 (hereafter “AS3″), the Flash Player realized significant performance gains. A few years ago, I ran some performance tests comparing JavaScript to AS2. ActionScript was noticably slower in every benchmark, usually in the range of 300% or more time required to perform large calculations. Even animation – Flash’s wheelhouse – was slower. AVM2 and AS3 have changed that, and levelled the playing field to a large degree.

That said, the greatest single contributor to slow performance of Flash applications is poorly written ActionScript. Below you’ll find 20 tips to help you write faster applications. Please note that the motivation behind each tip is performance, not transparency or best practice. A performance optimization might directly oppose a best practice, and will very often create opacity in your code. It’s your decision as to what takes priority in your application: performance, transparency or best practices.

Much of the information presented here will provide only negligible gains as regards performance when used in small-scale code operations. However, when a developer tweens 3500 raindrops every frame, every line of code needs to be parsed and pared to the sharpest, smallest point possible.

Before I begin, learn to benchmark your code. There are many approaches, including specialized software or components like frame-rate meters, but the simplest trick I know is to measure the time a loop containing the statements to be tested takes to operate. After all other setup is performed, on the line immediately before the loop, call the getTimer method and store it in a variable. Execute your loop (I usually start with 1000 iterations to avoid script timeout, then increase by a factor of 10 until I get a measurable metric). Immediately following the loop, evaulate the difference by invoking getTimer again and subtracting it from the first. E.g.,

var start:Number = getTimer();
2  for(var i:int = 0; i < 1000; i++){
3        // statements to evaluate
4 }
5  trace(getTimer() - start);


The Tips
1: Recycle Display Objects
Instantiating a Display Object can be a relatively heavy lift, especially for dynamic classes like MovieClip. When possible, instead of creating new DisplayObjects, recylce them and update visual properties and behavior. My post on using the Factory pattern for Display objects is a good example of this

2: Object literals are faster than the new operator
Strings, Numbers (including int and uint), and Boolean objects are usually instantiated using literal notation (var str:String = “text”). Objects and Arrays are often instantiated using the “new” operator – this is seldom necessary (only in the case of a fixed-length Array), and is slower..

1 var list:Array = [1,2,3];
is faster than
1 var list:Array = new Array(1,2,3);
  
3: For loops are faster than while and do-while loops
In AS3, the fastest loop is the for loop (not for.. or for each..in). While and do-while are slightly slower. Note that this is a marked contrast to both ActionScript 2 and JavaScript, where while and do-while are about 3 times faster than for loops. Also, the int datatype is faster than uint or Number (see tip #16).

1 for(var i:int=0;i<1000;i++)

is faster than

1  var i:int=0;
2  while(i<1000)

4: The in operator is faster than hasOwnProperty
To test if an object has a publically available property, use the “in” operator instead of the hasOwnProperty method.

1 prop in Object

is faster than

1 Object.hasOwnProperty("prop")

5: The one-dot rule
When accessing nested variables, anytime a variable requires even one level of scope shift to discover, and is referenced more than once, save it to local variable. In drawing classes, you’ll often see Math.PI / 2 referenced within loops that might iterated hundreds of times each frame – that value should be stored in a local variable. Note that this is not true when referencing member methods. Saving Math.PI to a variable is appropriate; saving Math.round to a variable is not.

1  var var1:Number = container.child.x;
2  textfield.text = var1;
3  container.child2.x = var1;
 
is faster than
 
1  textfield.text = container.child.x;
2  container.child2.x = container.child.x;
 
6: Dynamic classes are slow – use a public object variable instead
Avoid dynamic classes. Consider a public variable datatyped to Object instead. In a simple test, I extended Sprite and added a public data:* property, which could be used to store any type of dynamic information (attached to the data property rather than the instance itself). Running it over a few hundred thousand iterations, the extended Sprite was about 25% faster.

1  public class MyObject{
2      public var variables:Object = {};
3  }

is faster than

1  public dynamic class MyObject{
2  }

7: Perform work in enterframes
This one may seem counter-intuitive, and most tutorials and help-boards are peppered with warning against enter frames. Surprisingly, performing heavy calculation during the enterframe can help “balance the load” and lead to improved performance.

8: Standard string methods are faster than Regular Expressions
Regular Expressions are a great tool and can make complex tasks much easier. However, standard string methods are almost always faster.

1  someString.replace("abc", "");

is faster than

1  someString.replace(/abc/, "");

9: Use plain text instead of XML where possible
XML requires the VM to implement a parser, where evaluating plain text requires no special operation. Where possible, use strings or plain text rather than XML.

10: Work from the top of the stack
When manipulating arrays, work is performed from the top-down. That means always work from the end of the array, rather than the beginning. Pop and push are faster than shift and unshift.

1  MyArray.push(myVar);

is faster than

1  myTimer.addEventListener(TimerEvent.Timer, myFunc, false, 0, true);

12: Use local variables
When flash detects a variable, it looks first at the local scope – the context of the currently evaluating function. If not found, it looks at the next “highest” scope, and repeats, until the variable is discovered (or undefined is returned). Always use local variables when possible. Note that function arguments are considered local variables.

1  function getMinutes(){
2    var myVar:uint = 6000;
3    return getTimer() / myVar;
4  }

is faster than
 
1   var myVar:uint = 6000;
2   function getMinutes(){
3      return getTimer() / myVar;
4   }

13: Constants are faster than variables
Update 10/27/12 – if this was ever true, it no longer is.

14: Use the as operator instead of casting
Use the “as” operator when possible, instead of casting via constructor.
 
1  var child:MyClass = event.currentTarget as MyClass;
 
is faster than
 
1  var child:MyClass = MyClass(event.currentTarget);
 
15: E4X is extremely slow
E4X is another great tool that can offer drastic savings in development time, but when used on large data sets its performance tends to suffer. Surprisingly, looping through an XML tree is often much faster than complicated E4X structures.
 
1  for each(var node:XML in myXML){
2    if(node.@myProp == "value"){
3       if(node.@anotherProp == 12){
4          react();
5       }
6    }
7  }

is faster than
 
1  var nodes:XMLList = myXML.(@myProp=="value").(@anotherProp==12);  
2  for each(var node:XML in nodes){
3     react();
4  }

16: int – uint – Number
Use the appropriate number datatype. For simple incrementation (e.g., a for loop), ff the three Number datatypes, Number is the slowest, int is the fastest, and uint is slightly slower than int. If you’re dividing, use Number.
 
17: Use bitwise methods when possible
Figuring out the math behind bitwise operation can be intimidating, but there are simple ways to use bitwise functionality that can really speed up your code.
 
1  var n:Number = 9374.230498;
2  var m:Number = n | 0;

is faster than
 
1  var n:Number = 9374.230498;
2  var m:Number = Math.foor(n);

18: Use array.join instead of string concatenation
String concatenation is very slow – use arrays and array methods where possible.
 
1  var myArray:Array = [];
2  for(var i:int=0;i<1000;i++){
3    myArray[i] = "text";
4  };
5  var myString:String = myArray.join(" ");

is faster than
 
1  var myString:String = "";
2  for(var i:int=0;i<1000;i++){
3     myString += "text ";
4  };

19: Don’t evaluate in a loop’s conditional test
The condition expression in the for loop is commonly used to evaluate a variable or property – this expression should be a predefined local variable when possible, so that it doesn’t need to be evaluated during each iteration of the loop.
 
1  var l:uint = myArray.length;
2  for(var i:int=0;i>l;i++)

is faster than

1  for(var i:int=0;i>myArray.length;i++)
 

20: Stay DRY
The concept of DRY (Don’t Repeat Yourself) is commonly known and accepted, but in addition to more obivous benefits like maintainability and transparency, it can be a huge peformance boon. Whenever possible, perform an action as few times as possible. If several elements need to slide to the right, try to put them in a common parent and animate that rather than each instance. If there’s a method used frequently that isn’t dependant on instance state, use a static method. A very good example of this is filters – if you apply a GlowFilter on dozens of particles in an animation, performance will suffer – try putting the GlowFilter on a common parent and append each particle to that parent.
   
Conclusion
I hope the tips I’ve included can help you avoid some less-than-obvious pitfalls on the road to optimization.

I’ve tried to include information here that isn’t found in other AS3 optimization white papers, and tried to keep the superflous information to a minimum.

Additionally, there are optimization techniques found elsewhere that I question. For example, in AS3, variable name length doesn’t matter (it did in AS2).
 
1 var a:Number = 987234.230948;
  
is NOT faster than  
 
1 var myLongVariableNameUsedToMatterButDoesntAnymore:Number = 987234.230948;
 
I’ve also seen developers discourage typing inside a loop, stating that variables that will be referenced within the loop should be typed outside of it. My own testing does not prove that out.
 
1 var n:Number;
2 for(var i:int=0;i>10000;i++){
3   n = Math.random();
4 }

is NOT faster than
 
1  for(var i:int=0;i>10000;i++){
2    var n:Number = Math.random();
3  }

Last, a warning: don’t try to optimize your code until it’s thoroughly debugged. Make sure everything is working exactly as it should, even under challenging conditions, before worrying about performance. 


 

Visual Studio Keyboard Shortcuts

Playing with keyboard shortcuts is very interesting and reduce the headache of using the mouse again and again while programming with visu...