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:
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.
- 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);
}
[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.
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