====== 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"