Copyright © 2003 O'Reilly Media, Inc. All Rights Reserved.
Flash Remoting: The Definitive Guide
By Tom Muck
September 2003
ISBN: 0-596-00401-X
http://www.oreilly.com/catalog/flashremoting/
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

Components are one of the cornerstones of Rich Internet Application (RIA) development using Flash and Flash Remoting. Components make it easy to create rich interfaces, but they can also be responsible for an application getting bogged down with poor performance. Many of the Macromedia user interface components are very code-intensive when it comes to doing things like populating the component, sorting, and adding data. You should be conservative in your use of client-side code when dealing with components. The DataGrid component, for example, can utilize 90-100% of the end user's system resources while being populated—from a remote service or local data.

TIP: The Flash 2004 component architecture is optimized for applications that use five or six components; the shared library that Flash 2004 components require adds more file size than is justified by using only one or two components.

Component Speed

The speed of a component is often dependent on the type of code you are utilizing. What looks more efficient to the ActionScript programmer is often slower in execution. If you're in doubt about the speed of a particular section of code, you can time it using the Date( ) object. The code in Example 12-2 is a CodeTimer object, which can be used to time the execution of sections of code.

Example 12-2. The CodeTimer class facilitates easy timing of code

// Constructor accepts an optional <em>message</em> argument and starts the timer
function CodeTimer (message) {
 this.message = message; //optional message
 this.startTime = new Date( ).getTime( );
}

// <em>CodeTimer.trace( )</em> calculates the elapsed time and traces it to the Output window
CodeTimer.prototype.trace = function ( ) {
 this.endTime = new Date( ).getTime( );
 this.elapsedTime = this.endTime - this.startTime;
 if (this.message != undefined) trace(this.message);
 trace("Elapsed Time: " + this.elapsedTime + " milliseconds");
 return this.elapsedTime;
};

To use the timer, simply start it by creating a new CodeTimer object at the start of the code you want to time, and call the CodeTimer.trace( ) method at the end of the code:

// Initialize the timer
var t = new CodeTimer("Testing DataGlue");

// Some code to time
var fields = result_rs.getColumnNames( );
var idField = '#' + fields[0] + '#';
var descField = '#' + fields[1] + '#';
DataGlue.bindFormatStrings(cbName, result_rs, descField, idField);

// Display the elapsed time in milliseconds in the Output window
t.trace( );

The preceding example tests the speed of the DataGlue.bindFormatStrings( ) method. The example recordset that I tested yielded an average result of 10 milliseconds. The following code shows the time taken to manually populate the same ComboBox with the same recordset using an index loop:

var t = new CodeTimer("Testing index loop");

var fields = result_rs.getColumnNames( );
for (var i; i < result_rs.getLength( ); i++) {
 cbName.addItemAt(i,
 result_rs.getItemAt(i)[fields[1]],
 result_rs.getItemAt(i)[fields[0]]);
}
t.trace( );

The second example yielded an average result of 30 milliseconds. The last example uses a for...in loop and a much more concise coding style:

var t = new CodeTimer("Testing for/in loop");

for (var i in result_rs) cbName.addItem(i[0],i[1],root);

t.trace( );

The third example is less verbose and looks like it is more efficient, but it results in an elapsed time of 90 milliseconds—9 times slower than the first example—showing the efficiency of the DataGlue class. Looks are deceiving sometimes when coding, as this example demonstrates. It's best to not simply take the code at face value; measure your code's execution time and try different things to get the best possible results.

Shaving 80 milliseconds off the data-loading operation for one ComboBox can have a dramatic impact in the end user's experience if you have many user interface elements and can optimize some of them in similar ways. 80 milliseconds might not seem like a lot, but when you're dealing with a Flash interface it is wise to conserve where you can, to balance the initial load time of the movie, which might run into several seconds. 10 or 20 interface elements can increase this delay by 1 or 2 seconds for the user. Every little bit of optimization helps to improve the user experience.

Here is a simple technique for testing performance. When you're testing a brief operation, it may be so fast that you can't get an accurate measure of the time it takes. Furthermore, if an operation is performed hundreds or thousands of times during your program execution, the execution time may vary, and it isn't practical to add up the times of each execution manually. You can add a for...in loop to execute an operation, say, 100 times to give you a more accurate picture of the time required for execution. Here is the new code (additions shown in bold):

var t = new CodeTimer("Testing index loop 100 times");

for (var j; j < 100; j++) {
var fields = result_rs.getColumnNames( );
for (var i; i < result_rs.getLength( ); i++) {
cbName.addItemAt(i,
result_rs.getItemAt(i)[fields[1]],
result_rs.getItemAt(i)[fields[0]]);
}
}
// Displays total and average execution time
elapsedTime = t.trace( );
trace("Average Elapsed Time: " + elapsedTime/100 + " milliseconds");

Don't forget to take the average time, and don't forget to remove the for...in loop when you're done testing (you can comment out the testing code so that it is easy to reinstate if you want to test it again later). Be sure to test the operations that you are trying to optimize. How would the timing differ if the call to getColumnNames( ) were moved outside the second loop? Refer to "Looping" later in this chapter for hints on optimizing loop performance.

You can get much more elaborate with timing, but this simple example gives you the basic technique. Timing your code is very important when dealing with Flash Remoting or Flash interfaces in general. Sometimes, a call to the remote database will be quicker than trying to manipulate results on the client. Also, sometimes there are sections of code that execute too slowly in ActionScript and can be moved to the server.

Data Loading

Frequently, when building data-driven Flash interfaces, you test with a small amount of data with the knowledge that your application will grow in the future with more back-end data. The Flash UI components can generally handle a small amount of data, but when you start feeding thousands of records into them they start slowing down dramatically. How do you test with large amounts of sample data if the data doesn't exist yet?

One way is to use a temporary SQL statement in your server-side service that returns a lot more rows than your test data actually contains, by forcing a cross join on another table. A cross join gives you a result that contains every combination of rows of data from the joined tables. In other words, each row in the first table is joined to every row in the second table, giving you a huge resultset. A cross join has limited use in everyday data retrieval but is especially handy when you're testing application interfaces. Consider the following query on the Northwind database:

SELECT c.CategoryID, c.CategoryName
FROM Categories c

The query returns about eight rows of data. If you were to time this data being rendered into a ComboBox or DataGrid, you would not be able to discern any noticeable speed problem. Change the query to cross join the Products table:

SELECT c.CategoryID, c.CategoryName
FROM Categories c, products

Now the query returns over 600 rows by joining the Products table and returning a complete set of data from the Categories table for each row of data in the Products table. The fields that you are retrieving are the same as the fields in the first example, but they are duplicated many times. This gives you a better idea of what your final application will be able to handle and gives you a more accurate picture of where your bottlenecks are. (It also gives you a chance to test your screen layouts to see if they accommodate large recordsets and test your logic for pageable recordsets, if applicable.)

UI components can greatly speed up the development process, but they can also be a performance bottleneck if the code that uses the component is not optimized.