Optimizing applications

Improving client-side performance

Tuning software to achieve maximum performance is not an easy task. You must commit to producing efficient implementations and monitor software performance continuously during the software development process.

Employ the following general guidelines when you test applications for performance, such as using the getTimer() method and checking initialization time.

Before you begin actual testing, you should understand some of the influences that client settings can have on performance testing. For more information, see Configuring the client environment.

General guidelines

You can use the following general guidelines when you improve your application and the environment in which it runs:

  • Set performance targets early in the software design stage. If possible, try to estimate an acceptable performance target early in the application development cycle. Certain usage scenarios dictate the performance requirements. It would be disappointing to fully implement a product feature and then find out that it is too slow to be useful.

  • Understand performance characteristics of the application framework, and employ the strategies that maximize the efficiency of components and operations.

  • Understand performance characteristics of the application code. In medium-sized or large-sized projects, it is common for a product feature to use codes or components written by other developers or by third-party vendors. Knowing what is slow and what is fast in dependent components and code is essential in getting the design right.

  • Do not attempt to test a large application's performance all at once. Rather, test small pieces of the application so that you can focus on the relevant results instead of being overwhelmed by data.

  • Test the performance of your application early and often. It is always best to identify problem areas early and resolve them in an iterative manner, rather then trying to shove performance enhancements into existing, poorly performing code at the end of your application development cycle.

  • Avoid optimizing code too early. Even though early testing can highlight performance hot spots, refrain from fixing them while you are still developing those areas of the application; doing so might unexpectedly delay the implementation schedule. Instead, document the issues and prioritize all the performance issues as soon as your team finishes the feature implementation.

Testing applications for performance

You can use various techniques to test start-up and run-time performance of your applications, such as monitoring memory consumption, timing application initialization, and timing events. The Flex profiler provides this type of information without requiring you to write any additional code.

Calculating application initialization time

One approach to performance profiling is to use code to gauge the start-up time of your application. This can help identify bottlenecks in the initialization process, and reveal deficiencies in your application design, such as too many components or too much reliance on nested containers.

The getTimer() method in flash.utils returns the number of milliseconds that have elapsed since Adobe® Flash® Player or Adobe AIR™ was initialized. This indicates the amount of time since the application began playing. The Timer class provides a set of methods and properties that you can use to determine how long it takes to execute an operation.

Before each update of the screen, Flash Player calls the set of functions that are scheduled for the update. Sometimes, a function should be called in the next update to allow the rest of the code scheduled for the current update to execute. You can instruct Flash Player or AIR to call a function in the next update by using the callLater() method. This method accepts a function pointer as an argument. The method then puts the function pointer on a queue, so that the function is called the next time the player dispatches either a render event or an enterFrame event.

The following example records the time it takes the Application object to create, measure, lay out, and draw all of its children. This example does not include the time to download the SWF file to the client, or to perform any of the server-side processing, such as checking the Flash Player version, checking the SWF file cache, and so on.

<?xml version="1.0" encoding="utf-8"?> 
<!-- optimize/ShowInitializationTime.mxml --> 
<s:Application  
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark" 
    creationComplete="callLater(showInitTime)"> 
 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
 
    <fx:Script><![CDATA[ 
        import flash.utils.Timer; 
 
        [Bindable] 
        public var t:String; 
        private function showInitTime():void { 
            // Record the number of ms since the player was initialized. 
            t = "App startup: " + getTimer() + " ms"; 
        } 
    ]]></fx:Script> 
 
    <s:Label id="tb1" text="{t}"/>              
</s:Application> 

This example uses the callLater() method to delay the recording of the startup time until after the application finishes and the first screen updates. The reason that the showInitTime function pointer is passed to the callLater() method is to make sure that the application finishes initializing itself before calling the getTimer() method.

For more information on using the callLater() method, see Using the callLater() method.

Calculating elapsed time

Some operations take longer than others. Whether these operations are related to data loading, instantiation, effects, or some other factor, it's important for you to know how long each aspect of your application takes.

You can calculate elapsed time from application startup by using the getTimer() method. The following example calculates the elapsed times for the preinitialize and creationComplete events for all the form elements. You can modify this example to show individual times for the initialization and creation of each form element.

<?xml version="1.0" encoding="utf-8"?> 
<!-- optimize/ShowElapsedTime.mxml --> 
<s:Application  
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark" 
    initialize="init()" 
    height="750"> 
 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
 
    <fx:Script><![CDATA[ 
        import mx.collections.ArrayCollection; 
        
        [Bindable] 
        public var dp:ArrayCollection = new ArrayCollection ([ 
            {food:"apple", type:"fruit", color:"red"}, 
            {food:"potato", type:"vegetable", color:"brown"}, 
            {food:"pear", type:"fruit", color:"green"}, 
            {food:"orange", type:"fruit", color:"orange"}, 
            {food:"spinach", type:"vegetable", color:"green"}, 
            {food:"beet", type:"vegetable", color:"red"} 
        ]); 
 
        public var sTime:Number; 
        public var eTime:Number; 
        public var pTime:Number; 
 
        private function init():void { 
            f1.addEventListener("preinitialize", logPreInitTime, true); 
            f1.addEventListener("creationComplete", logCreationCompTime, true); 
        } 
 
        private var isFirst:Boolean = true; 
 
        private function logPreInitTime(e:Event):void { 
            // Get the time when the preinitialize event is dispatched. 
            sTime = getTimer(); 
            
            trace("Preinitialize time for " + e.target + ": " + sTime.toString());            
        } 
 
        private function logCreationCompTime(e:Event):void { 
            // Get the time when the creationComplete event is dispatched. 
            eTime = getTimer(); 
    
            /* Use target rather than currentTarget because these events are 
               triggered by each child of the Form control during the capture 
               phase. */ 
            trace("CreationComplete time for " + e.target + ": " + eTime.toString()); 
        } 
    
    ]]></fx:Script> 
 
    <s:Form id="f1"> 
        <s:FormHeading label="Sample Form" id="fh1"/> 
        <s:FormItem label="List Control" id="fi1"> 
            <s:List dataProvider="{dp}" labelField="food" id="list1"/> 
        </s:FormItem> 
        <s:FormItem label="DataGrid control" id="fi2"> 
            <s:DataGrid width="200" dataProvider="{dp}" id="dg1"/> 
        </s:FormItem> 
        <s:FormItem label="Date controls" id="fi3"> 
            <mx:DateChooser id="dc"/> 
            <mx:DateField id="df"/> 
        </s:FormItem> 
    </s:Form> 
</s:Application>

Calculating memory usage

You use the totalMemory property in the System class to find out how much memory has been allocated to Flash Player or AIR on the client. The totalMemory property represents all the memory allocated to Flash Player or AIR, not necessarily the memory being used by objects. Depending on the operating system, Flash Player or AIR will be allocated more or less resources and will allocate memory with what is provided.

You can record the value of totalMemory over time by using a Timer class to set up a recurring interval for the timer event, and then listening for that event.

The following example displays the total amount of memory allocated (totmem) to Flash Player at 1-second intervals. This value will increase and decrease. In addition, this example shows the maximum amount of memory that had been allocated (maxmem) since the application started. This value will only increase.

<?xml version="1.0"?> 
<!-- optimize/ShowTotalMemory.mxml --> 
<s:Application     
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark" 
    initialize="initTimer()"> 
 
    <fx:Script><![CDATA[ 
        import flash.utils.Timer; 
        import flash.events.TimerEvent; 
 
        [Bindable] 
        public var time:Number = 0; 
        [Bindable] 
        public var totmem:Number = 0; 
        [Bindable] 
        public var maxmem:Number = 0; 
 
 
 
        public function initTimer():void { 
            // The first parameter is the interval (in milliseconds). The 
            // second parameter is number of times to run (0 means infinity). 
            var myTimer:Timer = new Timer(1000, 0); 
            myTimer.addEventListener("timer", timerHandler); 
            myTimer.start(); 
        } 
        
        public function timerHandler(event:TimerEvent):void { 
            time = getTimer() 
            totmem = flash.system.System.totalMemory; 
            maxmem = Math.max(maxmem, totmem); 
        } 
    ]]></fx:Script> 
    
    <s:Form> 
        <s:FormItem label="Time:"> 
            <s:Label text="{time} ms"/> 
        </s:FormItem> 
        <s:FormItem label="totalMemory:"> 
            <s:Label text="{totmem} bytes"/> 
        </s:FormItem> 
        <s:FormItem label="Max. Memory:"> 
            <s:Label text="{maxmem} bytes"/> 
        </s:FormItem> 
    </s:Form> 
</s:Application>

Configuring the client environment

When testing applications for performance, it is important to configure the client properly.

Choosing the version of Flash Player

When you test your applications for performance, use the standard version of Adobe® Flash® Player or AIR rather than the debugger version of Flash Player or ADL, if possible. The debugger version of Flash Player provides support for the trace() method and the Logging API. Using logging or the trace() method can significantly slow player performance, because the player must write log entries to disk while running the application.

If you do use the debugger version of Flash Player, you can disable logging and the trace() method by setting the TraceOutputFileEnable property to 0 in your mm.cfg file. You can also set the omit-trace-statements compiler option to false.

You can keep trace() logging working, but disable the Logging API that you might be using in your application, by setting the logging level of the TraceTarget logging target to NONE, as the following example shows:

 myLogger.log(LogEventLevel.NONE, s);

For performance testing, consider writing run-time test results to text components in the application rather than calling the trace() method so that you can use the standard version of Flash Player and not the debugger version of Flash Player.

For more information about configuring trace() method output and logging, see Logging.

Disabling SpeedStep

If you are running performance tests on a Windows laptop computer, disable Intel SpeedStep functionality. SpeedStep toggles the speed of the CPU to maximize battery life. SpeedStep can toggle the CPU at unpredictable times, which makes the results of a performance test less accurate than they would otherwise be.

  1. Select Start > Settings > Control Panel.

  2. Double-click the Power Settings icon. The Power Options Properties dialog box displays.

  3. Select the Power Schemes tab.

  4. Select High System Performance from the Power Schemes drop-down box.

  5. Click OK.

Changing timeout length

When you test your application, be aware of the scriptTimeLimit property. If an application takes too long to initialize, Flash Player warns users that a script is causing Flash Player to run slowly and prompts the user to abort the application. If this is the situation, you can set the scriptTimeLimit property of the <s:Application> tag to a longer time so that the application has enough time to initialize.

However, the default value of the scriptTimeLimit property is 60 seconds, which is also the maximum, so you can only increase the value if you have previously set it to a lower value. You rarely need to change this value.

The following example sets the scriptTimeLimit property to 30:

<?xml version="1.0" encoding="utf-8"?> 
<!-- optimize/ChangeScriptTimeLimit.mxml --> 
<s:Application  
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark" 
    scriptTimeLimit="30"> 
 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
 
</s:Application>

Preventing client-side caching

When you test performance, ensure that you are not serving files from the local cache to Flash Player. Otherwise, this can give false results about download times. Also, during development and testing, you might want to change aspects of the application such as embedded images, but the browser continues to use the old images from your cache.

If the date and time in the If-Modified-Since request header matches the date and time in the Last-Modified response header, the browser loads the SWF file from its cache. Then the server returns the "304 Not Modified" message. If the Last-Modified header is more recent, the server returns the SWF file.

You can use the following techniques to disable client-side caching:

  • Delete the Flex files from the browser's cache after each interaction with your application. Browsers typically store the SWF file and other remote assets in their cache. On Microsoft Internet Explorer in Windows XP, for example, you can delete all the files in c:\Documents and Settings\username\Local Settings\Temporary Internet Files to force a refresh of the files on the next request. For more information, see Caching.

  • Set the HTTP headers for the SWF file request in the HTML wrapper to prevent caching of the SWF file on the client. The following example shows how to set headers that prevent caching in JSP:

     // Set Cache-Control to no-cache. 
     response.setHeader("Cache-Control", "no-cache"); 
     // Prevent proxy caching. 
     response.setHeader("Pragma", "no-cache");  
     // Set expiration date to a date in the past. 
     response.setDateHeader("Expires", 946080000000L); //Approx Jan 1, 2000 
     // Force always modified. 
     response.header("Last-Modified", new Date());

    Note that in some cases, setting the Pragma header to "no-cache" can cause a runtime error with GET requests over SSL with the HTTPService class. In this case, setting just the Cache-control header should work.

Reducing SWF file sizes

You can improve initial user experience by reducing the time it takes to start an application. Part of this time is determined by the download process, where the SWF file is returned from the server to the client. The smaller the SWF file, the shorter the download wait. In addition, reducing the size of the SWF file also results in a shorter application initialization time. Larger SWF files take longer to unpack in Flash Player.

The mxmlc compiler includes several options that can help reduce SWF file size.

Using the bytecode optimizer

The bytecode optimizer can reduce the size of the application's SWF file by using bytecode merging and peephole optimization. Peephole optimization removes redundant instructions from the bytecode.

Disabling debugging

Disabling debugging can make your SWF files smaller. When debugging is enabled, the Flex compilers include line numbers and other navigational information in the SWF file that are only used in a debugging environment. Disabling debugging reduces functionality of the fdb command-line debugger.

To disable debugging when using the command-line compiler, set the debug compiler option to false. The default value for the mxmlc compiler is false. The default value for the compc compiler is true. Setting this to false reduces the size of hte SWF file inside the SWC file.

For more information about debugging, see Command-line debugger.

Using the size report

The size-report compiler option outputs a high-level summary of the size of each type of data within your application's SWF file. You can use this information to identify problem areas in an application. For example, if you embed multiple fonts, you can compare the size that each embedded font takes up. If one font is considerably larger than the others, you could reduce the number of symbols embedded by that font.

The following example creates a size report called mysizereport.xml for the MainApp application:
mxmlc -size-report=mysizereport.xml MainApp.mxml
The following table describes the areas of the report:

Report tag

Description

<swf>

The compressed and uncompressed SWF file size, in bytes.

<headerData>

The size of the actual SWF file format header, product info, and other marker data.

<actionScript>

The ActionScript data associated with this SWF file. For non-debug SWFs, ActionScript bytecode (abc) and constant pool data is consolidated into a single block within the frame that it is associated with. For debug SWF or SWC files, the ActionScript bytecode blocks are generally broken down by symbol.

All entries are sorted largest to smallest.

<frames>

A frame by frame summary of all script and tag data for each frame. Applications usually consist of two or more frames, with the first frame containing all preloader logic as well as the top level SystemManager definition.

<frameData>

Additional SWF tags that are related to frame definitions. For example, frame labels, symbol definitions, export definitions, and showFrame tags.

<bitmaps>

The embedded bitmap data. All entries are sorted largest to smallest. If appropriate, the symbol name of each bitmap is provided.

The original file name of the embedded asset is not provided.

<fonts>

The embedded font data. All entries are sorted largest to smallest. If appropriate, the symbol name of each font is provided.

The original font face name or path is not provided, nor any enumeration of the embedded glyphs.

<sprites>

The sprite data. All entries are sorted largest to smallest. If appropriate, the symbol name of each sprite definition is provided.

<shapes>

The shape data. All entries are sorted largest to smallest. If appropriate, the symbol name of each shape definition is provided.

<bindaryData>

The generic binary data. This includes embedded SWF files, PixelBender shaders, or any other miscellaneous embed data. All entries are sorted largest to smallest. If appropriate, the symbol name of each data definition is provided.The original file name for each asset is not provided.

<sounds>

The embedded sound data. All entries are sorted largest to smallest. If appropriate, the symbol name of each sound is provided.The original file name for each asset is not provided.

<videos>

The embedded video data. All entries are sorted largest to smallest. If appropriate, the symbol name of each video asset is provided.The original file name for each asset is not provided.

The following is a sample size report:
<?xml version="1.0" encoding="UTF-8"?> 
<report> 
  <swf size="614011" compressedSize="318895"> 
 
    <!-- Header data (SWF attributes, product info, markers, etc.) --> 
    <headerData totalSize="533"> 
      <data type="metaData" size="465"/> 
      <data type="productInfo" size="28"/> 
      <data type="swfHeader" size="21"/> 
      <data type="fileAttributes" size="6"/> 
      <data type="scriptLimits" size="6"/> 
      <data type="backgroundColor" size="5"/> 
      <data type="endMarker" size="2"/> 
    </headerData> 
 
    <!-- Cumulative frame size summary. --> 
    <frames totalSize="613478"> 
      <frame name="_MultipleFaces_mx_managers_SystemManager" size="76648" frame="1"/> 
      <frame name="MultipleFaces" size="536830" frame="2"/> 
    </frames> 
 
    <!-- Actionscript code and constant data. --> 
    <actionScript totalSize="501609"> 
      <abc name="frame2" size="425053" frame="2"/> 
      <abc name="frame1" size="76556" frame="1"/> 
    </actionScript> 
 
    <!-- defineFont/2/3/4. --> 
    <fonts totalSize="97660"> 
      <font name="MultipleFaces__embed__font_myFont_medium_italic_1704731415" fontName="myFont" size="34180" frame="2"/> 
      <font name="MultipleFaces__embed__font_myFont_bold_normal_673776644" fontName="myFont" size="33472" frame="2"/> 
      <font name="MultipleFaces__embed__font_myFont_medium_normal_1681983489" fontName="myFont" size="30008" frame="2"/> 
    </fonts> 
 
    <!-- defineSprite. --> 
    <sprites totalSize="19"> 
      <sprite name="_MultipleFaces_Styles__embed_css_Assets_swf_mx_skins_cursor_BusyCursor_2036984981" size="19" frame="2"/> 
    </sprites> 
 
    <!-- defineShape/2/3/4. --> 
    <shapes totalSize="261"> 
      <shape size="261" frame="2"/> 
    </shapes> 
 
    <!-- SWF, Pixel Bender, or other miscellaneous embed data. --> 
    <binaryData totalSize="12506"> 
      <data name="mx.graphics.shaderClasses.SaturationShader_ShaderClass" size="2298" frame="2"/> 
      <data name="mx.graphics.shaderClasses.HueShader_ShaderClass" size="2268" frame="2"/> 
      <data name="mx.graphics.shaderClasses.SoftLightShader_ShaderClass" size="1920" frame="2"/> 
      <data name="mx.graphics.shaderClasses.LuminosityShader_ShaderClass" size="1282" frame="2"/> 
      <data name="mx.graphics.shaderClasses.ColorShader_ShaderClass" size="1272" frame="2"/> 
      <data name="mx.graphics.shaderClasses.ColorBurnShader_ShaderClass" size="1144" frame="2"/> 
      <data name="mx.graphics.shaderClasses.ColorDodgeShader_ShaderClass" size="1074" frame="2"/> 
      <data name="mx.graphics.shaderClasses.ExclusionShader_ShaderClass" size="624" frame="2"/> 
      <data name="mx.graphics.shaderClasses.LuminosityMaskShader_ShaderClass" size="624" frame="2"/> 
    </binaryData> 
 
    <!-- Additional frame tags (symbolClass, exportAssets, showFrame, etc). --> 
    <frameData totalSize="1423"> 
      <tag type="symbolClass" size="774" frame="2"/> 
      <tag type="exportAssets" size="539" frame="2"/> 
      <tag type="symbolClass" size="47" frame="1"/> 
      <tag type="frameLabel" size="43" frame="1"/> 
      <tag type="frameLabel" size="16" frame="2"/> 
      <tag type="showFrame" size="2" frame="1"/> 
      <tag type="showFrame" size="2" frame="2"/> 
    </frameData> 
  </swf> 
</report>

Using strict mode

When you set the strict compiler option to true, the compiler verifies that definitions and package names in import statements are used in the application. If the imported classes are not used, the compiler reports an error.

The following example shows some examples of when strict mode throws a compiler error:

 package { 
 	import flash.utils.Timer; // Error. This class is not used. 
 	import flash.printing.* // Error. This class is not used. 
 	import mx.controls.Button; // Error. This class is not used. 
 	import mx.core.Application; // No error. This class is used. 
  
 	public class Foo extends Application { 
 	} 
 }

The strict option also performs compile-time type checking, which provides a small optimization increase in the application at run time.

The default value of the strict compiler option is true.

Examining linker dependencies

To find ways to reduce SWF file sizes, you can look at the list of ActionScript classes that are linked into your SWF file.

You can generate a report of linker dependencies by using the link-report compiler option. This option takes a single file name. The compiler generates a report, and writes it to the specified file, that shows an application's linker dependencies in an XML format.

The following example shows the dependencies for the ProgrammaticSkin script as it appears in the linker report: