Adobe, MAKE SOME NOISE

Since I had my benchmark code already warmed up from Friday and a photoshop doc open with my histograms, I figured I should make good on the updated speed tests that I promised a year ago.

Here is basic math, receiving a huge boost between debug and release modes. The shaded bar is the duration of the call, so shorter means faster execution.

speed benchmark: actionscript math operators

From here down, everything is running 10m iterations. The scale of these graphs is 560ms. Here's the difference in calling a static public variable, versus a local copy of same.

speed benchmark: actionscript local var vs static public var

Finally, replacing the Math helper methods with some logic and basic math:

speed benchmark: actionscript math.pow squared
speed benchmark: actionscript math.pow cubed
speed benchmark: actionscript math.min
speed benchmark: actionscript math.max
speed benchmark: actionscript math.abs
speed benchmark: actionscript math.floor
speed benchmark: actionscript math.ceil
speed benchmark: actionscript math.round

These stack up pretty well with what I saw in the debug player a year ago (although there is an overall speed increase in all the tests).

If anyone is curious, there is a slowdown in calculating powers of a number (i * i * i * i) as you have to reference that variable over and over - for me the break-even between stringing on more 'i's and just calling Math.pow() was around 60.

I was messing around with some bitmap code this evening and I came across a performance bottleneck with some repeated calls to Math.random(). Since I haven't posted anything in a while, I decided to do some benchmarking and try out an alternate (psuedo) random number generator.

I chose the XOR algorithm, primarily because AS3 is lightning-quick with bit operations, and the algorithm is only 4 lines of code. Here are two versions I tried:

  1. // UINT
  2.  
  3. const MAX_RATIO:Number = 1 / uint.MAX_VALUE;
  4. var r:uint = Math.random() * uint.MAX_VALUE;
  5.  
  6. // returns a number from 0 - 1
  7. function XORandom():Number{
  8. r ^= (r << 21);
  9. r ^= (r >>> 35);
  10. r ^= (r << 4);
  11. return (r * MAX_RATIO);
  12. }

  1. // INT
  2.  
  3. const MAX_RATIO:Number = 1 / int.MAX_VALUE;
  4. const NEGA_MAX_RATIO:Number = -MAX_RATIO;
  5. var r:int = Math.random() * int.MAX_VALUE;
  6.  
  7. // returns a number from 0 - 1
  8. // comment out line 13 for -1 - 1
  9. function XORandom():Number{
  10. r ^= (r << 21);
  11. r ^= (r >>> 35);
  12. r ^= (r << 4);
  13. if(r < 0) return r * MAX_RATIO;
  14. return r * NEGA_MAX_RATIO;
  15. }

It's worth noting that this algorithm works from a seed number. For my purposes, picking one random seed and iterating is fine, but this function is predictable enough that you wouldn't want to use this method for crypto or anything where you'd want "very random" data sets. This also could be useful in some situations where you'd want to replay a set of random numbers - such as generating enemies or levels in a game and then creating a replay by saving the seed number.

For my benchmark, I ran 25 sets of 10 million iterations on each function, using the 10.0 release player in Safari.

acctionscript math.random optimization

Interestingly, the logic to gate negative numbers in the signed-int version was enough to push it out quite a bit past the uint version. All told, the uint algorithm runs in just under one fourth the time it takes to call Math.random().

As always, Wikipedia will tell you as much as you want to know about the ins and outs of this algorithm and how it stacks up against other generators. And, just for fun, here's a frequency graph of the output.

Get Adobe Flash player

Rotating objects toward a point

December 14th, 2009

I've seen a lot of questions about rotation in AS3 lately so I thought I'd post a quick writeup. It's a deceptively simple formula but there are a couple snags that can trip you up if you aren't aware of them.

Get Adobe Flash player

First off - Flash uses a fairly confusing rotational layout. A DisplayObject's rotation is measured in degrees from 0, which is at 3 O'Clock. Degrees extend clockwise to 9 O'Clock (180°) where an odd thing happens. Flash converts any number you pass in to the closest possible value to zero, so 181° gets recorded as -179°. This is still technically correct, but it can wreak havoc on your code if you're not expecting it.

Here's a quick method to get the positive angle back:

  1. spinner.rotation = 181;
  2. trace(spinner.rotation); // -179
  3. var angle:Number = spinner.rotation; // -179
  4. angle = (angle + 360) % 360 // 181

Alright - what people really want to know is how to calculate the angle between an object and the mouse, or another object. The solution is to take the arc-tangent of the offset in y, divided by the offset in x, which, if you remember your trig from highschool, would be the two perpendicular sides of your right triangle. Here's what the code looks like:

  1. package {
  2.  
  3. /*
  4. * Flash 10.0 ? ActionScript 3.0
  5. * www.calypso88.com
  6. */
  7.  
  8. //--------------------------------------
  9. // PACKAGES
  10. //--------------------------------------
  11.  
  12. import flash.display.Sprite;
  13. import flash.display.DisplayObject;
  14. import flash.geom.Point;
  15. import flash.events.Event;
  16.  
  17. public final class Arrow extends Sprite {
  18.  
  19. //--------------------------------------
  20. // CONSTRUCTOR
  21. //--------------------------------------
  22.  
  23. public function Arrow(){
  24. super();
  25. addEventListener(Event.ENTER_FRAME, enterFrame);
  26. }
  27.  
  28. //--------------------------------------
  29. // EVENT HANDLERS
  30. //--------------------------------------
  31.  
  32. private function enterFrame(e:Event):void{
  33. var p1:Point = new Point(this.x, this.y);
  34. var p2:Point = new Point(stage.mouseX, stage.mouseY);
  35. this.rotation = angleBetween(p2, p1);
  36. }
  37.  
  38. //--------------------------------------
  39. // PRIVATE & PROTECTED INSTANCE METHODS
  40. //--------------------------------------
  41.  
  42. private function angleBetween(p1:Point, p2:Point):Number{
  43. var deltaX:Number = p1.x - p2.x;
  44. var deltaY:Number = p1.y - p2.y;
  45. var angle:Number = Math.atan2(deltaY, deltaX); // in radians
  46. return radiansToDegrees(angle);
  47. }
  48.  
  49. private function radiansToDegrees(r:Number):Number{
  50. return(r * (180 / Math.PI));
  51. }
  52.  
  53. private function degreesToRadians(d:Number):Number{
  54. return(d * (Math.PI / 180));
  55. }
  56. }
  57. }

The last surprise is that the arc-tangent comes back in radians (π radians = 180° degrees) so you'll need to convert back to degrees before you set the rotation. Here is the final result applied to a few dozen arrows.

Get Adobe Flash player

Feeds worth reading

October 8th, 2009

Two friends asked for my list of RSS links within a week, so I figured I'd scrape together the sites that I like to keep an eye on for industry news, cool programming, and cutting edge Flash work. The site links are all on a list here, or you can download the set in OPML for direct injection into a feed reader.

Links | OPML

Flattening arrays

September 21st, 2009

A big danger when working with large sets of data is the possibility of introducing a small slowdown that's compounded with every piece of data in the set. A common example is the multidimensional array; it's a powerful tool, but every time you look into the array you have to go at least two lookups deep to find your data. Normally that's not a big deal but if you're touching the set thousands of times per frame, it adds up quickly.

A fairly drastic solution is to flatten your arrays - it will give you a nice performance boost when you access the data, but it comes at the cost of obfuscation. Here's how it works:

flat_array

(Sorry the numbers are goofy between the diagram and the code - CS4 crashed and I didn't feel like re-making that graphic)

In the traditional 2D view each "row" is a new Array object - this is where the added computing cost comes in. By flattening the data into one super-long array, you can access any piece of data with just one lookup. Finding the correct cell is a little more tricky though.

  1. var myMDArray:Array = [
  2. ['0a', '0b', '0c', '0d', '0e'],
  3. ['1a', '1b', '1c', '1d', '1e'],
  4. ['2a', '2b', '2c', '2d', '2e']];
  5.  
  6. var myFlatArray:Array = ['0a', '0b', '0c', '0d', '0e', '1a', '1b', '1c', '1d', '1e', '2a', '2b', '2c', '2d', '2e'];
  7.  
  8. var columns:int = 5;
  9.  
  10. trace(myMDArray[2][4]);
  11. trace(myFlatArray[14]);
  12. trace(myFlatArray[(2 * columns) + 4]);
  13. // 2e, 2e, 2e

Now - supposing you have an index for something and want to reverse engineer the row and column - just break out the modulo grid code:

  1. var row:int = int(14 / columns);
  2. var col:int = 14 % columns;
  3.  
  4. trace(row, col);
  5. // 2, 4