====== AS3 Speed tests ====== This is a community maintained page of speed optimization techniques for use with ActionScript3. Originally started on [[http://www.rockonflash.com/blog/?p=63|RockOnFlash.com]], the post just grew with new revisions and tests and yet new techniques from the community! Only made sense to put it on a wiki for everyone to maintain ;) ==== General Notes ==== Many of these enhancements are particular to the type of variable declaration you use. So far, in testing **Number** vs. **int**, the greatest improvements are gained when using **int**s. In general (with exceptions), only moderate if any improvements are seen when using **Number**-declared variables, due to the limited accurate operations. The following excellent generalizations come from **Andre Michelle**’s website (link below): - Do not overload condition and statements as in AS2 It astounds me that this once-excellent technique for optimizing for speed no produces faster code. Overloading *should* produce faster code by reducing the number of compiled instructions and generating faster ones. - Do not use Objects, if you know which properties will be finally involved. - Cast instances, while reading from an Array - Try to use Integers (int) for all possible cases - Bitoperators are lightning fast (Other things we what would like to see in AS3: - Typed, length-fixed Arrays. “…this would increase the speed…” - A faster solution to read/write pixels on a bitmap.) ==== Optimization Links ==== [[http://rozengain.com/?postid=35|Dennis Ippel - Some ActionScript 3.0 Optimizations and links]] [[http://blog.andre-michelle.com/2005/as3-optimations-suggestions|Andre Michelle - General Guidelines for AS3 Optimizations]] ===== Integer Addition & Increment/Decrement ===== Tests done to determine if "a += b" really is faster (as it **should** be) than "a = a + b" have been inconclusive so far. I'd still recommend using the shorthand if you're accustomed to it, mostly because I'm hoping a new compiler will grant faster speeds for using it. The good news is that Incrementing (adding 1) with the ++ operator is far faster than "a = a + 1" - in fast, using the ++ four times (in consecutive statements) is still about 50% faster than "a = a + 4"!! Unfortunately, //**the same is not true with the decrement (--) operator**//. So far, tests on that have not shown any consistent speed increase over "a = a - 1". (sigh) Here's the testing code: import flash.utils.getTimer; var time : Number = getTimer(); var sumStd : Number = 0; var sumOpt : Number = 0; var sumMpt : Number = 0; var loops : Number; var thresh : Number = 0.002; function runStdTest():void { time = getTimer(); for (var i:int = 0; i < 20000000; i++) { var test:int = i; test = test - 1; } sumStd += (getTimer() - time); } function runOptTest():void { time = getTimer(); for (var i:int = 0; i < 20000000; i++) { var test:int = i; test--; } sumOpt += (getTimer() - time); } function runEmptyTest():void { time = getTimer(); for (var i:int = 0; i < 20000000; i++) { var test:int = i; } sumMpt += (getTimer() - time); } loops = 10; for (var i:int = 0; i < loops; i++) { runStdTest(); runOptTest(); runEmptyTest(); } trace ("Standard vs. Optimization - Mean over", loops, "loops"); trace ("Standard Test: ", (sumStd /= loops)); trace(); ===== Division ===== ==== divide by 2 ==== Now, you've probably heard the tip about "use multiplication instead of division when dividing by 2", right? faster: var n:Number = value *.5; slower: var n:Number = value / 2; run it with a test: import flash.utils.getTimer; var time:Number = getTimer(); function runDivisionTest():void { time = getTimer(); for(var i:int=0;i<10000000;i++) { var test:Number = i/2; } trace("DivisionTest: ", (getTimer()-time)); } function runMultTest():void { time = getTimer(); for(var i:int=0;i<10000000;i++) { var test:Number = i*.5; } trace("MultTest: ", (getTimer()-time)); } runDivisionTest(); runMultTest(); traces out: DivisionTest: 162 MultTest: 110 Alright, so it's not miles faster, but in a 3D engine like [[http://blog.papervision3d.org|Papervision3D]], this becomes the difference between a fast engine and a slow engine real quickly. Well, there's one still that's even faster: Bitwise shift operations Divide by 2 with a right shift: trace(10 >> 1); // traces: 5 Multiply by 2 with a left shift: trace(10 << 1); // traces: 20 Now, run the test against the Division and Multiplication tests above: function runBitTest():void { time = getTimer(); for(var i:int=0;i<10000000;i++) { var test:int = i >> 1; } trace("BitTest: ", (getTimer()-time)); } runBitTest(); traces out: DivisionTest: 152 MultTest: 112 BitTest: 63 HOLY CRAP Batman - that's nearly 1/2 the speed?? or should I say, 1*.5 the speed ;) Well, **kind of** - but **you may not want to use the bitshift**. When the BitTest code above uses the the same (slower) **Number** declaration for **test**, the results are slightly different (btw, the iterator **i** in each testing loop also now has the same declaration (**int**) in each code section =)): Division by 2 - Mean over 20 loops DivisionTest: 83.3 MultTest: 62.9 BitRightTest: 45.85 EmptyCodeTest: 29.1 I've also thrown in an empty loop timing for comparison - so a more accurate comparison can be made by subtracting that timing from each of the others. These gave: Net timing results: DivisionTest: 54.2 MultTest: 33.8 BitRightTest: 16.75 So both optimizations are still faster with **Number** declarations. **But don't use BitShifting for Number operations.** That's because the bit shift will round off the result to an integer - AS3 turns your original value into an integer before making the bit shift. Unless that's what you're after, **use "0.5 *" instead.** ===== Math.floor/Math.ceil ===== So, I was looking at some other stuff with uint and this seems to be a real gem when it comes to Math.floor and Math.ceil operations. I was reading through Flash's help (I have to say, I really enjoy the new help - I live in there) on uint and realized that, like int, when you pass a number, it strips everything passed the decimal (like Math.floor). So, I thought, what the hey, let's give er' a speed test. Sure enough, it was much faster to use uint(n) than Math.floor(n) - nearly 10x's as fast ** -- UPDATE -- ** After Paulius Uza's comments about using int, I went back and added tests for int with the floor/ceil tests, and sure enough, they're even faster than using uint. floor's test wasn't that drastic, but ceil's was 1/2 the time of uint's version ** -- /UPDATE -- ** fast: var test:int = int(1.5); //test equals 1 slow: var test:Number = Math.floor(1.5); //test equals 1 Now, I know what yer thinkin': what about Math.ceil? +1? yes ;) fast: var test:int = int(1.5)+1; //test equals 2 slow: var test:Number = Math.ceil(1.5); //test equals 2 Time for a test: function runFloorTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = 1.5; var test:Number = Math.floor(n); } trace("FloorTest: ", (getTimer()-time)); } function runUintFloorTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = 1.5; var test:uint = uint(n); } trace("UintFloorTest: ", (getTimer()-time)); } function runIntFloorTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = 1.5; var test:int = int(n); } trace("IntFloorTest: ", (getTimer()-time)); } function runCeilTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = 1.5; var test:Number = Math.ceil(n); } trace("CeilTest: ", (getTimer()-time)); } function runUintCeilTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = 1.5; var test:uint = n == uint(n) ? n : uint(n)+1; } trace("UintCeilTest: ", (getTimer()-time)); } function runIntCeilTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = 1.5; var test:int = n == int(n) ? n : int(n)+1; } trace("IntCeilTest: ", (getTimer()-time)); } function runBitShiftFloorTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = 1.5; var test:Number = n >> 0; } trace("BitShiftFloorTest: ", (getTimer()-time)); } runFloorTest(); runUintFloorTest(); runIntFloorTest(); runBitShiftFloorTest(); runUintCeilTest(); runIntCeilTest(); traces out: FloorTest: 1733 UintFloorTest: 176 *IntFloorTest: 157 BitShiftFloorTest: 178 UintCeilTest: 650 *IntCeilTest: 384 ===== Math.abs ===== I was thinking about another one that I use sometimes which was using *-1 instead of Math.abs on a number faster: var nn:Number = -23 var test:Number= nn < 0 ? nn * -1 : nn; slower: var nn:Number = -23 var test:Number = Math.abs(nn); Lets test! function runABSTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = -1.5; var test:Number = Math.abs(n); } trace("ABSTest: ", (getTimer()-time)); } function runABSMultTest():void { time = getTimer(); for(var i:uint=0;i<10000000;i++) { var n:Number = -1.5; var test:Number = n < 0 ? n * -1 : n; } trace("ABSMultTest: ", (getTimer()-time)); } runABSTest(); runABSMultTest(); traces out: ABSTest: 1615 *ABSMultTest: 153 So... Taking this a bit further, instead of multiplying by -1, what about just assigning the inverse? The code is updated so **n** (instead of **test**) receives the new **Abs** value: function runAbsTest():void { time = getTimer(); for (var i:int = 0; i < 10000000; i++) { var n:Number = -1.5; n = Math.abs (n); } sumAbs += (getTimer() - time); } function runMm1Test():void { time = getTimer(); for (var i:int = 0; i < 10000000; i++) { var n:Number = -1.5; n = n < 0 ? n * -1 : n; } sumMm1 += (getTimer() - time); } function runChsTest():void { time = getTimer(); for (var i:int = 0; i < 10000000; i++) { var n:Number = -1.5; n = n < 0 ? -n : n; } sumChs += (getTimer() - time); } function runEmptyTest():void { time = getTimer(); for (var i:int = 0; i < 10000000; i++) { var n:Number = -1.5; n = n; } sumMpt += (getTimer() - time); } And what if we just tested to see if **n** was less than zero **before** doing anything? function runIfmTest():void { time = getTimer(); for (var i:int = 0; i < 10000000; i++) { var n:Number = -1.5; if (n < 0) n = -n; } sumIfm += (getTimer() - time); } Traced out: Abs - Mean over 25 loops AbsTest: 1572.4 MultByMinus1Test: 121.96 ChsTest: 95.76 IfMinusTest: 76 EmptyTest: 32.48 Net timing results: AbsTest: 1539.92 MultByMinus1Test: 89.48 ChsTest: 63.28 IfMinusTest: 43.52 Bear in mind the **IfMinusTest** code assumes the inverse operation must take place **every** time (n is always < 0) - so the actual implementation would probably run even faster. ===== Math.sqrt ===== In response to a question posted on how to determine distance, someone said "sqrt is an expensive operation". So I tried it out against the old Babylonian Method that was known (as recently as the late 20th century =)!) to be quite accurate once you'd run it through a few iterations: import flash.utils.getTimer; var time : Number = getTimer(); var sumSqrt : Number = 0; var sumAppr : Number = 0; var sumMpt : Number = 0; var loops : Number; var thresh : Number = 0.00001; var a : Number = 0, b : Number = 0, c : Number = 0, w : Number = 0; var count : int = 0; w = 147.29; trace ("Actual value =", Math.sqrt (w)); // This is what AS3 gives us. trace ("Testing threshold:", thresh); b = w * 0.25; do { count++; c = w / b; b = (b + c) * 0.5; a = b - c; if (a < 0) a = -a; } while (a > thresh); // This tests the algorithm. c = Math.sqrt (w); trace ("Number of iterations for approximation:", count); trace ("Approximation:", b); trace ("Error in approximation:", b - Math.sqrt (w)); trace (" - - - -"); function runSqrtTest():void { time = getTimer(); for (var i:int = 0; i < 1000000; i++) { var test:Number = Math.sqrt (w); } sumSqrt += (getTimer() - time); } function runApprTest():void { time = getTimer(); for (var i:int = 0; i < 1000000; i++) { var b :Number = w * 0.25, a :Number; c :Number; do { c = w / b; b = (b + c) * 0.5; a = b - c; if (a < 0) a = -a; } while (a > thresh); } sumAppr += (getTimer() - time); } function runEmptyTest():void { time = getTimer(); for (var i:int = 0; i < 1000000; i++) { var n:Number = w; } sumMpt += (getTimer() - time); } loops = 20; for (var i:int = 0; i < loops; i++) { runSqrtTest(); runApprTest(); runEmptyTest(); } trace ("Math.sqrt - Mean over", loops, "loops"); trace ("Math.sqrt() Test: ", (sumSqrt /= loops)); trace ("Approximation Test: ", (sumAppr /= loops)); trace ("Empty Test: ", (sumMpt /= loops)); trace ("Net timing results:"); trace ("Math.sqrt() Test:", (sumSqrt - sumMpt)); trace ("Approximation Test:", (sumAppr - sumMpt)); I could not believe the results: Actual value = 12.136309158883519 Testing threshold: 0.00001 Number of iterations for approximation: 6 Approximation: 12.13630915888352 Error in approximation: 1.7763568394002505e-15 - - - - Math.sqrt - Mean over 20 loops Math.sqrt() Test: 172.05 Approximation Test: 134.8 Empty Test: 4.05 Net timing results: Math.sqrt() Test: 168 Approximation Test: 130.75 Okay, what gives? Even my math co-processor on my old 286 (that I **still** do my taxes on) could do better than that! And when we boost the testing threshold up for one less iteration, we still get 10-digit accuracy, but with nearly a 1/3 speed boost: Actual value = 12.136309158883519 Testing threshold: 0.002 Number of iterations for approximation: 5 Approximation: 12.136309166280597 Error in approximation: 7.397078505277932e-9 - - - - Math.sqrt - Mean over 20 loops Math.sqrt() Test: 167.3 Approximation Test: 114.95 Empty Test: 3.95 Net timing results: Math.sqrt() Test: 163.35000000000002 Approximation Test: 111 Speed Increase: 32.047750229568415 % I can't help feeling like I'm missing something here :T If I'm not, then this algorithm will be faster than Math.sqrt. UPDATE by K2xL.com@gmail.com: Whoever posted this tip must've used an older version of Flash. I ran the Math.sqrt improvement test with Flash 9 v. 115 today on March 26th, 2008. The results were the opposite. I think Adobe optimized their Math.sqrt. Here are the results I got when running your code: Actual value = 12.136309158883519 Testing threshold: 0.00001 Number of iterations for approximation: 6 Approximation: 12.13630915888352 Error in approximation: 1.7763568394002505e-15 - - - - Math.sqrt - Mean over 20 loops Math.sqrt() Test: 36.65 Approximation Test: 60.1 Empty Test: 26.8 Net timing results: Math.sqrt() Test: 9.849999999999998 Approximation Test: 33.3 ===== Summary ===== ==== Number ==== **Operation** >>>> "**faster code**" Division by 2 »» "n * 0.5" Multiply by 2 »» "n + n" Math.floor »» "n >> 0" Absolute value »» "if (n < 0) n = -n"