Monday, April 30, 2012

Silverlight Startup Best Practices

Introduction
Startup is important because it is the first interaction that your user has with your application.  You get one chance to impress, and failure to do so could mean the user closing and/or uninstalling your application permanently.  What follows is a set of tips and tricks you can use to supercharge the startup path of your Silverlight application. 
The Cardinal Rule
There is, essentially, a single cardinal rule.
     Do the absolute minimum required to display your main screen. 
Write this on a sticky note and hang it on your monitor.  This may seem like common sense (I like to think so), but I’ve analyzed many an application that violates this rule in more ways than one.  The less deterministic the code is that you’re executing, the more you’re asking for trouble.  To subdivide this rule into some specific examples:
  • Minimize your download size.
  • Never wait on network I/O before displaying your main screen.
  • Minimize disk I/O, delay-load any data or business logic that you can. 
Minimize your download size
Since your application has to be downloaded before it can start up, your download size directly affects startup time.  Consider dividing your application into multiple XAP files.  The first XAP should include only what is necessary to display your main screen and provide core functionality.  Tim Heuer has put together a great video on silverlight.net titled “Loading Dynamic XAPs and Assemblies” that explains this concept in detail.  Use this method to componentize and delay load parts of your application that pull in large dependencies and/or don’t absolutely needto be available when your application starts up. 
Another great tip on Silverlight XAP compression comes from David Anson.  A Silverlight XAP is just a renamed Zip file, but as of Silverlight 4 our XAP compression algorithm isn’t as efficient as it could be.  By simply re-zipping your XAP files using an optimal compression algorithm you can shave about 20% off your download size (as high as 70% in extreme cases).  Check out David’s post, “Smaller is better!” for the details and a useful script to help you automate the process.
Never wait on network I/O
No-doubt, this is one of the riskiest things you can do during startup.  Network I/O latency is non-deterministic; when you make a call to a web service or access data from a network share your request could return in 2 milliseconds, 2 seconds, 2 minutes, or it may never return at all!  If your application waits for this data to be retrieved before showing your UI, it may nevershow up.  At best, you are gambling on the speed of your network connection to determine your startup time.
Minimize disk I/O
When you increase the amount of data that you load from disk (whether raw data files or loading unnecessary assemblies) you increase the amount of time that you’re waiting for physical media.  It takes a substantial amount of time for your hard disk to seek to each new read location, and it takes time to read the data once you seek there.
Load Fewer Assemblies at Startup
One way to minimize disk I/O is to reduce the number of assemblies that your application loads.  In Silverlight, a new assembly is loaded the first time that the CLR just-in-time (JIT) compiles a method that accesses a type from it.  For example, if you took a simple “Hello World” Silverlight application and added a DataGrid to it, this would cause System.Windows.Controls.Data.dll (where DataGrid lives) to be loaded.  You can use the 
VMMap tool from Windows Sysinternals to discover which assemblies you are loading, and how much data from each one.  Assemblies can be found under the ‘Images’ category as seen in the screenshot below:

Since an assembly isn’t loaded until you pull in one of its types, there are two scenarios to keep in mind:
1.    Static members can cause dependencies to be loaded eagerly.  For example, if you have a static field of type DataGrid, System.Windows.Controls.Data.dll will be loaded at application startup during static initialization.  This can be avoided by leveraging System.Lazy<T>, see the MSDN documentation for details.  More information on lazy initialization can be foundhere.
  1. A method that conditionally loads a new dependency can be refactored so that the assembly is loaded when it’s actually needed.
    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        
    bool b = true;
        
    if (b == true)
        {
            
    // Instead of this, refactor into a method, this will
            // prevent System.Windows.Controls.Data.dll where
            // DataGrid lives (and which is not one of the core
            // SL assemblies) from getting loaded.
            //   DataGrid myDataGrid = new DataGrid();
            //   LayoutRoot.Children.Add(myDataGrid);

            AddDataGrid();
        }
    }
//prevent in-lining if method implementation is too small.
[
MethodImpl(MethodImplOptions.NoInlining)]
public void AddDataGrid()
{
    
DataGrid myDataGrid = new DataGrid();
    LayoutRoot.Children.Add(myDataGrid);
}
Load Less Data
Avoid loading content/configuration data that isn’t needed to display your main screen.  An example could be an email client where a user’s messages are all serialized into a flat file.  You should wait to load your message data until after your main window has loaded, and you’ve had a chance to display a loading animation or otherwise show that your application is responsive.
Another example could be an application that has a rich extensibility and plugin model.  Make sure that you are loading and displaying the main shell of your application before you load each of your plugins.  A single misbehaving plugin could add lots of extra time to your startup sequence! 
Optimizing Templates and Styles
Having some Xaml parsed at startup to define the initial appearance of your application is unavoidable.  Because of this, be sure to optimize your initial Xaml design for startup time.  Here are a few things you should keep in mind when optimizing your Xaml.
  • Minimize your element count.  Every element that you add to the visual tree adds to the amount of time that it takes to parse.   When refactoring your Xaml, you may find yourself with elements left over in the visual tree that no longer contribute to function or appearance.  A good example of this could be a Grid with a single child and no meaningful properties set. These types of elements should be removed!
  • Remove dead XAML.  If a style or a part of your tree is no longer used, remove it!  Why pay for something that you’re never going to see or use?
  • Prefer Templates over UserControls.  UserControls need to be re-parsed per instantiation, templates are parsed only once.  This is especially important in say, a DataGrid cell template, where hundreds of cells are styled the same way.  If you style your cell using a UserControl, you are going to parse the same Xaml hundreds of times over; by using a template you ensure that this Xaml is only parsed once!
  • Don’t set properties to their default values, this includes things like setting Opacity=”1”, or setting RenderTransforms to non-parameterized values.  Some of our design tools have a nasty habit of doing this so you may need to police their output.  We’re working to make this better in the future. 
Consider Using a Splash Screen
So you’ve implemented the rest of the startup best practices, but your application is still not quite snappy enough for you?  At this point you should consider using a splash screen.  By default, Silverlight applications use a default splash screen (the spinning orbs that I’m sure you’re familiar with).  By replacing the default splash screen you can greatly improve perceived performance and get your own custom branding in front of the user as soon as possible.  There is an MSDN article available here that explains the process of setting a custom splash screen.
 A custom splash screen in conjunction with the Xap chaining example in Tim’s “Loading Dynamic XAPs and Assemblies” video mentioned under the “Minimize your download size” section can go a long way toward providing a responsive user experience. 
Conclusion
Application startup can be a tricky thing to master, but once you’ve successfully driven your first real application to a fast startup the principles learned will serve you for years to come. We’ll be updating and correcting this page going forward, so add a bookmark and send the link out to other developers on your team.

No comments:

Post a Comment