The Flash player executes bytecode slowly. You can see here that a simple function that adds one to a number takes a significant amount of time to execute.
<canvas height="100">
<simplelayout/>
<button>Test
<method event="onclick">
tester.doTest();
</method>
</button>
<node name="tester">
<attribute name="reps" value="50000"/>
<method name="doTest">
<![CDATA[
var f = function ( a ){ return a + 1; }
var t = new Date();
for ( var i = reps; i >=0; i-- ){ f( i ); }
t = ( new Date() ) - t;
report.addText( ' ' + ( t/reps ) );
]]>
</method>
</node>
<text name="report" resize="true">
Avg function time:
</text>
</canvas>
Compare this to the JavaScript interpreter in your browser. This is pretty much the same test, and your browser will show you that it takes significantly less time to execute. Depending on your browser/flash player combination this difference may be as much as 400x.
<script>
function doTest(){
var reps = 50000;
var f = function ( a ){ return a + 1; }
var t = new Date();
for ( var i = reps; i >=0; i-- ){ f( i ); }
t = ( new Date() ) - t;
alert( 'Avg function time:' + ( t/reps ) );
}
</script>
<button onclick="doTest()">Test</button>
Laszlo targeted the Flash runtime because it's fairly ubiquitous, and because it's a good and consistent software renderer. It also, of course, at least has a bytecode interpreter, which was a requirement for us, but it's not a very performant one -- and that has made it harder than it should be to both evolve the Laszlo platform, and to develop Laszlo apps.
Performance is always a huge consideration when developing client software, but it is especially so for rich internet apps, where the user's expectation has been set by quick rendering HTML pages. Often, the improved experience that an RIA offers ultimately makes a slight pause during startup worthwhile, but if users bail before the app starts up, then they'll never get a chance to know this.
I have spent a lot of time optimizing both the Laszlo runtime and Laszlo applications. The good news is that even the most awful seeming Laszlo app can usually be optimized and made to startup quickly and perform adequately. There are a number of strategies for this, and hopefully I will someday cover all of them here. For this entry, though, I have chosen to focus on Laszlo's initstage attribute, which offers the developer some choices around how portions of a Laszlo program are initialized.
This discussion does not include an overview of Laszlo's Krank feature, which is often an extremely simple and efficient way of optimizing a Laszlo app. Krank does, however, introduce some complications into the deployment path, and it also makes apps significantly bigger.
The basic strategy I want to demonstrate here is to defer the creation of views which aren't needed when a program starts. The example below contains a class which simulates a component that takes a long time to initialize. Note also the use of the <inittimer> tag, which provides an easy way to track app startup time.
For the purposes of demonstration, I've made it so you have to click the HTML button below to load the Laszlo app. This allows you to see a slow starting app without making this whole page sluggish.
<canvas height="200">
<inittimer/>
<class name="slowComponent" width="150" height="16" bgcolor="gray">
<!-- time in ms to wait before initing -->
<attribute name="extratime" value="500"/>
<method name="init">
<![CDATA[
var d = new Date();
while ( (new Date() ) - d < extratime ){};
super.init();
]]>
</method>
</class>
<button y="25" onclick="slowin.show()"> Show window
</button>
<window name="slowin" x="100" width="200" height="200" visible="false"
title="slow window">
<method name="show">
this.setVisible( true );
</method>
<view name="content">
<simplelayout spacing="2" />
<slowComponent/> <slowComponent/>
<slowComponent/> <slowComponent/>
<slowComponent/> <slowComponent/>
</view>
</window>
</canvas>
As you can see, this app takes a little over three seconds to initialize, mostly because of the half-second delay caused by each instance of slowComponent.
Since we don't need the window when the app starts (let's say it's a preferences dialog or similar) we can defer the creation of the window contents by using Laszlo's initstage attribute.
<canvas height="200">
...
<window name="slowin" x="100" width="200" height="200" visible="false"
title="slow window">
<method name="show">
this.setVisible( true );
content.completeInstantiation();
</method>
<view name="content" initstage="defer">
<simplelayout spacing="2" />
<slowComponent/> <slowComponent/>
<slowComponent/> <slowComponent/>
<slowComponent/> <slowComponent/>
</view>
</window>
</canvas>
The call to completeInstantiation in the window's show method ensures that the window contents get created when the window is shown. As an aside, you might imagine that the instantion of a declarative program (especially one which can include constraints where a refers to b and b refers to a) is somewhat complex. It certainly is, and I don't want to cover how a Laszlo program initializes here. However, the initstage tag simply governs how a node and its parent initialize.
"Defer" on a view means that the view's parent should not create that view until completeInstantiation is called. Since the semantics of 'init' are such that a view calls init when all of its subviews have inited, you will note that the content view in the example above is not inited until completeInstantiation is called. I encourage you to copy and edit this program to verify this for yourself. Finally, it's worth noting that it's fine to call completeInstantiation repeatedly.
So this fixes the startup time problem, but we're still left with an ugly pause when the user hits the button. There's another improvement we can make here, which is to show the window before all of the slowComponents have inited. Again, Laszlo provides a fairly simple construct to make this possible.
<canvas height="200">
<inittimer/>
<class name="slowComponent" initstage="late">
<!-- time in ms to wait before initing -->
<view width="150" height="16" bgcolor="gray">
<attribute name="extratime" value="500"/>
<method name="init">
<![CDATA[
var d = new Date();
while ( (new Date() ) - d < extratime ){};
Debug.write( 'init' );
super.init();
]]>
</method>
</view>
</class>
<button y="25" onclick="slowin.show()"> Show window
</button>
<window name="slowin" x="100" width="200" height="200" visible="false"
title="slow window">
<method name="show">
this.setVisible( true );
content.completeInstantiation();
</method>
<view name="content" initstage="defer">
<simplelayout spacing="2" />
<slowComponent/> <slowComponent/>
<slowComponent/> <slowComponent/>
<slowComponent/> <slowComponent/>
</view>
</window>
</canvas>
Note that the structure of slowComponent had to change a little to make this possible. The reason for this change turns on the meaning of initstage="late". This can be confusing, and in fact, when I went to write this last example, I simply tried this:
<class name="slowComponent" width="150" height="16" bgcolor="gray"
inistage="late">
...
But this has no effect. Why? The answer is that initstage="late" means: "init my children in the background, and allow my parent to init without me." However, completeInstantion means: "force my children to init right now." When completeInstantiation is called on the content view, it forces the initialization of all of the slowComponents. Since the slowComponents have no children, they simply apply their attributes and run their init routine, even though they are inistage="late".
With the change made in the last example above (where it's the child of slowComponent that takes a long time to init,) the content view instructs its children to initialze, but those children are themselves initstage="late", meaning that their children get put on a trickle queue, and their construction and initialization happens in small slices in the background, even while the program takes user-input and processes other interrupts. It should finally be noted that initstage="late" views can be forced to init completely by calling completeInstantiation on them, just like deferred views.
Fortunately, a real component that takes a long time to initialize is usually just a container for complex, slow-to-initialize contents. This means that it's often enough to set initstage="late" on classes or intstances of classes.
One last note about initstage="late": it may be tempting to simply use it on, say, the window that is not shown when the program starts. In practice this is usually a bad idea because the views that are getting contructed and initialized in the background slow down the flash player to a user-perceptible degree. It's almost always preferable to defer the creation of a container for views that aren't needed at startup-time, and then to use initstage="late" on those contents.
We used some of these techniques to speed up the start of Laszlo's homepage app. You'll recognize the behavior of the dropdown windows, where you can see the late initialization of the window contents when they are first dropped down.