Monday, April 28, 2008

Optimizing Actionscript 2.0 - a bytecode perspective - Part I

Lately I have beeing looking at ways to optimize some actionscript 2.0 code.
I had two goals, the first was to reduce the size of the .swf file and the second was to make some parts of it faster at runtime.

I will try and write a series of posts describing what I have learned and still am learning from this.

Generally speaking - in most cases optimizing for speed comes at the expence of space and vice versa. also there are isues with how easy the code is to maintain after optimizing. With all that in mind I started looking at my code.

At first I was looking at the code at the action script level, but very soon i decided that If I realy want to understand what is gooing on under the hood I will have to look at bytecode (or actually P-Code at that stage).

Tools



The first thing that drew my attantion when I looked at the flasm output .flm file for my .swf was the repeating pattern

push 'com'
getVariable
push 'name'
getMember
push 'space'
getMember
push 'parts'
getMember
push 'functionName'
getMember


that was allover the place.
could it be?
each type that I write com.name.space.parts.function() it does all that ?
but wait - I have an import statement - is it not chaching those refrences somehow ?
argh... is that the price for deveoping a nice heirarchical class library ?

o.k. - lets cool down - lets try a simple example.

Lets look at the .flm of a project that includes two classes:


class com.epeleg.utils.Debug {
public static function traceThis(s) {
trace(s)
}
}


and a Main class:


import com.epeleg.utils.Debug;
class Main
{
public static function main():Void
{
Debug.traceThis('hello');
Debug.traceThis('world');
}
}


Lets build (in release mode) this and look at the results:
The .swf was 425 bytes.

if you realy must (and if you are like me - you probably do) see the entire .flm that was created Click Here:


otherwise (or afterwards) lets focus for a moment on the following:



push r:0, 'main'
function2 () (r:1='this')
push 'hello', 1, 'com'
getVariable
push 'epeleg'
getMember
push 'utils'
getMember
push 'Debug'
getMember
push 'traceThis'
callMethod
pop
push 'world', 1, 'com'
getVariable
push 'epeleg'
getMember
push 'utils'
getMember
push 'Debug'
getMember
push 'traceThis'
callMethod
pop
end // of function


this is the code that is generated by the compiler, yep - read it again...

now if I would go to optimize this by manipulating the code at this level I could probably do something like:


push r:0, 'main'
function2 () (r:1='this')
push 'world','hello', 1, 'com'
getVariable
push 'epeleg'
getMember
push 'utils'
getMember
push 'Debug'
getMember
dup
push 'traceThis'
callMethod
pop
push 'traceThis'
callMethod
pop
end // of function



well, this might work for some people, personally - (at least now) I had no intentions for manipulating the code at this level - what I wanted was a way to change my actionscript to force the compiler to create something smarter.

Attempt 2
change the main class to:
a Main class:


import com.epeleg.utils.Debug;
class Main
{
public static function main():Void
{
var TR=Debug.traceThis;
TR('hello');
TR('world');
}
}


and looking at the appripriate section again (sorry - no full code again) we now have:
push r:0, 'main'
function2 () (r:1='this')
push 'com'
getVariable
push 'epeleg'
getMember
push 'utils'
getMember
push 'Debug'
getMember
push 'traceThis'
getMember
setRegister r:2
pop
push 'hello', 1, r:2, UNDEF
callMethod
pop
push 'world', 1, r:2, UNDEF
callMethod
pop
end // of function

o.k. now this looks better, only thing is that the .swf is now 428 bytes.
this is reasonable beacuse we added the new variable to the constant pull.

also what is this UNDEF ?

lets try a different version for class main:

import com.epeleg.utils.Debug;
class Main
{
public static function main():Void
{
var DGB=Debug;
DGB.traceThis('hello');
DGB.traceThis('world');
}
}


swf size is still 428 bytes, but the ugly UNDEF is gone,


push r:0, 'main'
function2 () (r:1='this')
push 'com'
getVariable
push 'epeleg'
getMember
push 'utils'
getMember
push 'Debug'
getMember
setRegister r:2
pop
push 'hello', 1, r:2, 'traceThis'
callMethod
pop
push 'world', 1, r:2, 'traceThis'
callMethod
pop
end // of function


is This realy better - The Quick answer is No, actually the one with the UNDEF is better then the last one.
Why ? I will try and explain on the next Post where we wil take a closer look at the actual bytecode.

Thats all for now.

No comments:

 
Clicky Web Analytics