Now that you had time to read my previous post, How to improve performance in your Metro style app , on the methodology and tools available to help you create fast and fluid apps, I want to dig into the common performance killers I have seen in apps. In this post, I go over the top guidance points which I have observed to result in measureable and noticeable improvements for Metro style apps, both written in JavaScript and in XAML. Plus, I tell you about 5 specific practices that I know will make a big difference, regardless of what language you are using. The good news is that they don’t depend on clever tricks or complex operations. I am confident that following these guidelines will hugely improve your app’s performance. Let me know in the comments how these help you, and share your own tips.

General guidance

Rely on packaged content, not network content.

  • Local images and files are always faster to retrieve than those over the network.
  • If your app needs to load a “live” image, it is best to use a local image as a placeholder while it is being retrieved.

Scale local images to the right size.

  • If an image is always going to be displayed at the same resolution, then package it at that resolution pre-scaled. This prevents the image from being scaled at runtime each time it is shown, incurring a performance penalty many times throughout the app’s lifecycle.
  • If the image may be shown at multiple resolutions, package multiple versions of the image unless you have a strong reason not to.

Keep the launch times of your modern app fast.

  • Perform network operations only after the splash screen disappears.
  • Defer loading databases and other large in-memory objects while the app is activating.
  • If you have large tasks to complete, provide a custom splash screen or pared down landing page so that your app can accomplish these tasks in the background.

Windows 8 runs on a wide range of devices, use media content appropriate for the user's resolution.

  • Loading content that is too small for a user’s resolution has reduced fidelity.
  • Loading content that is too large for a user’s resolution puts an unnecessary strain on the resources of the system.

Emphasize responsiveness in your apps.

  • Don’t block the UI thread with synchronous APIs. Use asynchronous APIs or call synchronous APIs in a non-blocking context (such as from another thread).
  • Move time-intensive calculations to a thread that is not the UI thread. This is important because users will likely notice delays longer than 100ms.
  • Break apart time-consuming work into smaller chunks, allowing the UI thread to listen for user input in between.
  • Use web workers/threads to offload expensive work.
  • Don’t draw to the screen faster than it can refresh. Input events fire much faster than the screen refresh rate. So using these events to update the screen causes a large amount of unnecessary work. Synchronize your draws to the screen refresh rate instead.

Tweaking performance of Metro style apps using JavaScript

When coding Metro style apps in JavaScript, all the tips and tricks you already know for coding JavaScript in web sites still apply. But for Metro style apps written in JavaScript, here are the top 3 techniques that yield the most impactful performance gains, based on our research and observations. For more info, see Performance best practices for Metro style apps using JavaScript, in the Dev Center.

Use thumbnails for quick rendering

The file system and media files are an important part of most apps, and also one of the most common sources of performance issues. Accessing media files can be slow, because it takes memory and CPU cycles to store and decode or display the media.

Instead of scaling down a large version of an image to display as a thumbnail, use the Windows Runtime thumbnail APIs. Scaling down a full-size image is inefficient because the app must read and decode the full-size image and then spend additional time scaling it. The Windows Runtime provides a set of APIs backed by an efficient cache to allow the app to quickly get a smaller version of an image to use for a thumbnail.

This example prompts the user for an image and then displays the image.

// Pick an image file
picker.pickSingleFileAsync()
.then(function (file) {
var imgTag = document.getElementById("imageTag");
imgTag.src = URL.createObjectURL(file, false);
});

This example works well when you want to render the image at, or near, its full size, but it's inefficient for displaying a thumbnail view of the image. The thumbnail APIs return a thumbnail version of the image that the app can decode and display much faster than the full-sized image. The next example uses the getThumbnailAsync method to retrieve the image and create a thumbnail based on it.

// Pick an image file
picker.pickSingleFileAsync()
.then(function (file) {
var properties = Windows.Storage.FileProperties.ThumbnailMode;
return file.getThumbnailAsync(properties.singleItem, 1024);
})
.then(function (thumb) {
var imgTag = document.getElementById("imageTag");
imgTag.src = URL.createObjectURL(thumb, false);
});

Our measurements show that this pattern can improve image loading times by as much as 1000% (that is, the image loads up to 10 times faster when using the thumbnail APIs versus accessing it directly from disk and scaling it).

Keep DOM interactions to a minimum

In the platform for Metro style apps using JavaScript, the DOM and the JavaScript engine are separate components. Any JavaScript operation that involves communication between these components is relatively expensive when compared to operations that can be carried out purely within the JavaScript runtime. So it's important to keep interactions between these components to a minimum. For example, getting or setting properties of DOM elements can be fairly expensive. This example accesses the body property and several other properties repeatedly.

// Don’t use: inefficient code. 
function calculateSum() {
// Retrieve Values
var lSide = document.body.all.lSide.value;
var rSide = document.body.all.rSide.value;

// Generate Result
document.body.all.result.value = lSide + rSide;
}

function updateResultsStyle() {
if (document.body.all.result.value > 10) {
document.body.resultsDiv.class = "highlighted";
} else {
document.body.resultsDiv.class = "normal";
}
}

The next example improves the code by caching the value of the DOM properties instead of repeatedly accessing them.

function calculateSum() {
// Retrieve Values
var all = document.body.all;
var lSide = all.lSide.value;
var rSide = all.rSide.value;

// Generate Result
all.result.value = lSide + rSide;
}

function updateResultsStyle() {
var body = document.body;
var all = body.all;
if (all.result.value > 10) {
body.resultsDiv.class = "highlighted";
} else {
body.resultsDiv.class = "normal";
}
}

Use DOM objects only to store info that directly affects how the DOM lays out or draws elements. If the lSide and rSide properties from the previous example store only info about the internal state of the app, don't attach them to a DOM object. The next example uses pure JavaScript objects to store the internal state of the app. The example updates DOM elements only when the display needs to be updated.

var state = {
lValue: 0,
rValue: 0,
result: 0
};

function calculateSum() {
state.result = lValue + rValue;
}

function updateResultsStyle() {
var body = document.body;
if (result > 10) {
body.resultsDiv.class = "highlighted";
} else {
body.resultsDiv.class = "normal";
}
}

Our measurements show that simply accessing data in the DOM can result in up to a 700% increase in access time when compared to accessing variables not tied to the DOM.

Manage layout efficiently

To render an app onscreen, the system must perform complex processing that applies the rules of HTML, CSS, and other specifications to the size and position of the elements in the DOM. This process is called a layout pass and can be very expensive.

APIs that trigger a layout pass include:

  • window.getComputedStyle
  • offsetHeight
  • offsetWidth
  • scrollLeft
  • scrollTop

A layout pass occurs as part of calling these APIs if anything that affects layout has changed since layout info was last gathered. One way to reduce the number of layout passes is to batch API calls that trigger a layout pass. To see how to do this, let's take a look at the next code snippet. In both of the examples here, we are adjusting the offsetHeight and offsetWidth of an element by 5. First, take a look at a common but inefficient way to adjust the offsetHeight and offsetWidth:

// Don't use: inefficient code.
function updatePosition(){
// Calculate the layout of this element and retrieve its offsetHeight
var oH = e.offsetHeight;

// Set this element's offsetHeight to a new value
e.offsetHeight = oH + 5;

// Calculate the layout of this element again because it was changed by the
// previous line, and then retrieve its offsetWidth
var oW = e.offsetWidth;

// Set this element's offsetWidth to a new value
e.offsetWidth = oW + 5;
}

// At some later point the Web platform calculates layout again to take this change into account and render
the element to the screen

The previous example triggers 3 layout passes. Now take a look at a better way to achieve the same result:

function updatePosition() {
// Calculate the layout of this element and retrieve its offsetHeight
var height = e.offsetHeight + 5;

// Because the previous line already did the layout calculation and no fields were changed, this line retrieves
the offsetWidth from the previous line

var width = e.offsetWidth + 5;

//set this element's offsetWidth to a new value
e.offsetWidth = height;

//set this element's offsetHeight to a new value
e.offsetHeight = width;
}

// At some later point the system calculates layout again to take this change into account and render element to the screen

Even though the second example is only slightly different than the first, it triggers only 2 layout passes instead of 3, a 33% improvement. The performance impact varies, depending on the size and complexity of the DOM and its associated styles. The richer your app’s UI, the more important following this guideline becomes.

Tweaking performance of Metro style apps written in XAML

Well-structured XAML pays dividends in many key scenarios including activation time, page navigation, and memory usage. Here are a few tips to help tune the XAML of your app.

Avoid duplication

Parsing XAML and creating corresponding objects in memory can be time-consuming for complex UI with deep element trees. This is why we recommend that you load only the XAML needed to get past the startup process. The easiest thing to do is load only pages needed to display the first visual. Carefully examine the XAML of your first page to make sure it contains everything it needs. If you reference a control or style that is defined somewhere else, the framework parses that file too.

<!--This is the first page an app displays. A resource used by this page, TextColor, is defined in 
AppStyles.xaml which means that file must be parsed when this page is loaded. Because AppStyles.xaml
contains many app-wide resources, all of these resources need to be parsed even though they aren’t
necessary to start the app. -->

<Page ...>
<Grid>
<TextBox Foreground="{StaticResource TextColor}" />
</Grid>
</Page>

Contents of AppStyles.xaml
<ResourceDictionary>
<SolidColorBrush x:Key="TextColor" Color="#FF3F42CC"/>

<!--many other resources used across the app and not necessarily for startup.-->
</ResourceDictionary>

Trim resource dictionaries. Store app-wide resources in the Application object to avoid duplication, but move resources specific to single pages to the resource dictionary of that page. This reduces the amount of XAML parsed when the app starts and only incur the cost of parsing that XAML when a user navigates to the specific page.

<!--Bad: XAML which is specific to a page should not be included in the App’s resource
dictionary. The app incurs the cost of parsing resources that are not immediately
necessary at startup, instead of parsing on demand. These page specific resources should be
moved to the resource dictionary for that page.-->
<Application>
<Application.Resources>
<SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="ThirdPageTextColor" Color="#FF3F42CC"/>
</Application.Resources>
</Application>

XAML for the home page of the app
<Page ...>
<StackPanel>
<TextBox Foreground="{StaticResource HomePageTextColor}" />
</StackPanel>
</Page>

XAML for the second page of the app
<Page ...>
<StackPanel>
<Button Content="Submit" Foreground="{StaticResource SecondPageColor}" />
</StackPanel>
</Page>
 
<!--Good: All page specific XAML has been moved to the resource dictionary for the
page on which it’s used. The home page specific XAML was not moved because it must
be parsed at start up anyway. Moving it to the resource dictionary of the home page
wouldn’t be bad either.-->
<Application>
<Application.Resources>
<SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/>
</Application.Resources>
</Application>

XAML for the home page of the app
<Page ...>
<StackPanel>
<TextBox Foreground="{StaticResource HomePageTextColor}" />
</StackPanel>
</Page>

XAML for the second page of the app
<Page ...>
<Page.Resources>
<SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/>
</Page.Resources>

<StackPanel>
<Button Content="Submit" Foreground="{StaticResource SecondPageTextColor}" />
</StackPanel>
</Page>

Optimize element count

The XAML framework is designed to display thousands of objects but reducing the number of elements on a page will make your app layout and render scenes even faster. There are a few tricks that you can use to reduce a scene’s element count while maintaining the same level of visual complexity.

  • Avoid unnecessary elements. For example, set the Background property on panels to provide a background color instead of placing colored Rects behind the elements in that panel.
<!--Bad XAML uses an unnecessary Rectangle to give the Grid a black background-->                                                                
<Grid>
<Rectangle Fill="Black"/>
</Grid>
 
<!--Good XAML uses the Grid’s background property-->
<Grid Background="Black" />
  • Collapse elements that are not visible because they are blocked or transparent.

  • If the same vector-based element is re-used multiple times, consider making it an image. Memory for an image is allocated only once per image Uri. In contrast, vector-based elements are instantiated for every instance.

Use independent animations

Animations can be calculated from beginning to end when they are created. Sometimes the changes to the property being animated don’t affect rest of the objects in a scene. These are called independent animations and they are run on the composition thread instead of the UI thread. This guarantees that they remain smooth because the composition thread is updated at a consistent cadence. All these are guaranteed to be independent:

  • Object animations using key frames
  • Zero-duration animations
  • Canvas.Left/Top
  • UIElement.Opacity
  • SolidColorBrush.Color when applied to many properties
  • Subproperties of
    • UIElement.RenderTransform
    • UIElement.Projection
    • RectangleGeometry of UIElement.Clip

Dependent animations affect layout and can’t be calculated without extra input from the UI thread. These animations include modifications to properties like width and height. By default dependent animations are not run and require an opt-in from the app developer. When enabled, they will run smoothly if the UI thread remains unblocked; but they begin to stutter if the framework or app is doing a lot of other work.

The XAML framework has worked hard to make almost all animations independent by default; but there are some actions you can take to disable this optimization. Be very cautious when doing the following:

  • Setting the EnableDependentAnimation property allows a dependent animation to run on the UI thread. Convert these animations into an independent version. For example animate ScaleTransform.XScale and ScaleTransform.YScale instead of the Width and Height of an object.
  • Making per frame updates which are effectively dependent animations. An example of this is applying transformations in the handler for CompositonTarget.Rendering.
  • Running any animation considered independent in an element with CacheMode=BitmapCache. This is considered dependent because the cache must be re-rasterized each frame.

Creating fast and fluid apps that are rich and complex is an art. I shared some specific things for you to consider, but it is rare that any one tool or technique is the key to good performance. More often it is a collection of good practices starting with considering performance early in your design. I hope that this blog will set you on the right path for delivering on your customers’ high expectations.

Happy (and efficient) coding!

-Dave Tepper, Program Manager, Windows