Heavy constructor JIT optimisation in ActionScript
I was running FlexPMD the other day over some code and it brought up this warning: "HeavyConstructor. Constructor must be as lightweight as possible. No control statement allowed, whereas a cyclomatic complexity of 2 has been detected. The Just-In-Time compiler does not compile constructors. Make them as lightweight as possible, or move the complexity of the code to a method called by the constructor. Then the complexity will be compiled by the JIT."
Huh? That's news to me, actionscript constructors are not compiled if they have a conditional statement in them?
The FlashPlayer is a Just In Time (JIT) compiler just like Java. What this means is that is that it will convert the code in a swf into native machine code and run that. That's the main reason the flash player is so fast!
A Flex application, when it is compiled the code gets converted from MXML to ActionScript to ActionScript Byte Code (ABC) which is then packaged up into a swf. When that swf is run in the Flash Player it gets compiled again by the JIT compiler from ABC code to native machine code.
If you want to know more about the flash virtual machine take a look at the ActionScript Virtual Machine 2 (AVM2) Overview.
While it doesn't say much about JIT compilation it does contain this footnote:
"In practice, the AVM2 may transform the code at run-time by means of a JIT, but this does not affect the semantics of execution, only its performance."
Notice the word "may" there so you code may or may not be converted to machine code.
Why would this be the case? JIT compilation is sometimes a trade off and it can be faster to not JIT a piece of code but just interpret the code. It takes time to convert the byte code to native machine code. If the code is only going to run once it may run faster if it is not converted.
It turns out that the JIT compiler doesn't compile ActionScript class constructors.
The solution is to make your constructors as light as possible, if you need a constructor to do a lot of work it's a good idea to move all of the code into a private init method and call that from the constructor as the init method will be compiled and optimized by the JIT.
So if you have code like this:
2{
3 if (condition) {
4 .... code here ....
5 }
6 ... more code here ...
7}
Change it to be like this:
2{
3 init();
4}
5
6private function init():void {
7 if (condition) {
8 .... code here ....
9 }
10 ... more code here ...
11}
For some classes with a single simple condition in the constructor the extra overhead of calling another method may be slower. So if you're not sure run some timing tests.
For more information take a look at ActionScript 3.0 and AVM2: Performance Tuning which goes into a lot more detail and what the Flash Player does under the hood.
Initialization functions ($init, $cinit) are interpreted
Everything else is JITted
A user defined object's constructor is different from $init and $cinit. I downloaded open source sdk, unassembled a few pure action script and mxml programs using SwfxPrinter, and clearly saw that $init, $cinit and the class's constructor were three different entities that did not seem to call each other at all. Further black tests proved to indicate no difference.
If someone has code that clearly depicts performance degradation with large constructors, please post them. I even tried statics with constructors and saw the compiler cleverly move this code into $init leaving the constructor with other stuff that seemed jittable... This mystery has been bothering me alot :)
The JIT here we are talking about is conversion of ABC bytecode to native machine code so disassembling SWFs is probably not going to be much help as you wouldn't know which bits are JITed and which bit are not. It's all internal to the Flash player I would assume.
I've seen reference to the issue in other place and generally the word constructor is used. eg Section 4.1 in http://je2050.joa-ebert.com/files/misc/as3opt.pdf
Unassembling code shows you the $cinit and $init functions - these are "interpreted" - and I am assuming everything else is jitted (assuming that PDF is the bible). Since none of the constructors code gets moved over to $init, once can assume that the constructor is not interpreted.
Now if you have static variables defined, you can see that it gets moved into $init and therefore you know for sure that it is interpreted. Thats how the disassembly helps in pointing to some hints - i.e if code moved into $init or $cinit it is interpreted.
I'm not the authority on this, so this message is just to stimulate a discussion ...
I had assumed that $init was for statics and $cinit for the constructor - which may or may not be the case. We assume these 2 blocks of code are not JITed but again it's hard to know for sure.
And of course new versions of the Flash Player (10 and 10.1) may do things totally differently.
I'll do some further investigation and see what I can come up some information.
You need to make a release version of the swf and run it in the non debug version of the Flash Player. Running it in the debug player shows that the init method is usually slower.
Just curious - how did you collect your timing? I have trace statements that do not show up in release versions. Did you display the timing inside a textbox or something?
Rather than using trace statements I just set the text property of some labels to show the results like so:
private function onCreationComplete():void {
result1.text = testA().toString();
...
}
private function testA():int {
var noRuns:int = 500000;
var start:int = getTimer();
for (var i:int = 0; i < noRuns; i++) {
var a:ClassA = new ClassA();
}
var end:int = getTimer();
return end-start;
}
...
<s:Label id="result1" />
...
Hope that helps.
In debug player.
Non empty constructor: 639
Constructor calling non empty init: 840
Empty constructor and calling non empty init: 837
In release player:
Non empty constructor: 120
Constructor calling non empty init: 115
Empty constructor and calling non empty init: 106
(smaller numbers are faster)
There's not a lot of difference here but the results are always consistent in that calling a constructor always takes more than having it call init.