Building Windows 8 blog
Windows Store for developers blog
Visual Studio blog
The Windows blog
Inside SkyDrive blog
Download Windows 8 Release Preview
Windows Dev Center
Follow us @WinDevs
The //build/ conference
Developer forums
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.
Rely on packaged content, not network content.
Scale local images to the right size.
Keep the launch times of your modern app fast.
Windows 8 runs on a wide range of devices, use media content appropriate for the user's resolution.
Emphasize responsiveness in your apps.
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.
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 filepicker.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 filepicker.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).
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.
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:
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.
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.
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>
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.
<!--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" />
<!--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.
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:
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:
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