Live News

Optimizing your XAML app for performance (10 by 10)

Over the past few weeks, we’ve highlighted a number of compelling new platform capabilities in our Windows 10 by 10 development series that you can implement in your Windows 10 apps to provide a better experience to your users. One of the important things to always take into consideration is app performance. Here are some tips to improve app performance in Windows 10 apps.

1.      Recompile your apps for Windows 10

The first thing to be aware of is that a lot of performance work has been done in Windows 10 to improve apps that are simply recompiled for Windows 10. To illustrate, take a look at this comparison for a few apps that showcases the improvements by simply recompiling:

 1_memUsage  2_startupTime
Memory usage Start up time

For more guidance on how to move from Windows Runtime 8.x to UWP, check out the MSDN documentation.

2.      Use the Visual Studio profiler to measure and track performance

At the core of performance optimization of Windows 10 apps is the Visual Studio Profiler. To use the profiler, go to Debug à Profiler à Start Diagnostic Tools Without Debugging… from the Visual Studio 2015 menu or simply hit Alt+F2.

3_visualStudioProfiler

The profiler will give you valuable information regarding your UWP app’s performance with these five tools:

  • Application Timeline
    • Examine where time is spent in your app. Useful for:
      • Correlating user actions with application activity so you can filter to applicable time ranges for trouble spots
      • Troubleshooting timing issues like low frame rate
  • CPU Usage
    • See where the CPU is spending time executing your code. Useful when the CPU is the performance bottleneck
  • GPU Usage
    • Examine GPU usage in your DirectX app. Useful to determine whether the CPU or GPU is the performance bottleneck
  • Memory Usage
    • Investigate app memory to find issues such as memory leaks
  • Network
    • Examine information about each network operation in your app, including HTTP request and response headers, payloads, cookies, timing data and more

These tools will capture traces in their respective areas to help you analyze your app’s performance. With the exception of Memory Usage, the tools can be combined to capture data simultaneously, which can then be correlated over multiple areas of interest.

4_profilerInAction

In the above example, you can see the diagnostic traces captured on the Application Timeline, including CPU and GPU usage. At the top, there’s UI thread utilization, which provide colored bar charts that relate to the stages of XAML drawing (parsing, layout and render), I/O performance and the app code execution. At the bottom of the screen, the exact events that occur and associated durations are shown. With this, you can quickly see what is causing slowdowns in your app and identify areas for further investigation. For example, a lot of long orange Layout executions might indicate drawing a lot of items on screen, which may point to virtualization not being applied (read on for further details on virtualization).

Doing a memory analysis will provide us with a good idea of how much memory the app is using over time. This can indicate memory leaks (if memory keeps increasing over time without decreasing) or give us pointers on where to optimize for memory usage by using smaller images for example (read on for further details on image and text optimization).

5_profilerMemory

To learn more about the new profiling tool in Visual Studio 2015, read the in depth blog post by the WPF team.

3.      List UI Virtualization

A common app scenario is displaying a list of items for users to browse a collection. These lists can grow very large, which can lead to poor performance. By default, the ListView and GridView controls support UI virtualization of the elements in the list. In short, UI virtualization means that only the items that are visible on screen, plus a small window of adjacent items, are created graphically. The process of expanding a DataTemplate and assigning it values from the data list is called realization. Schematically, virtualization looks like this:

6_virtualizedList
Source: ListView basics and virtualization concepts (Alain Zanchetta)

Additionally, UI virtualization also ensures that realized items are recycled when users scroll in the list. This means that the created elements are reused, populated with new data, and then displayed again. This saves the cost of having to destroy and recreate the visual elements, which greatly improves performance.

As mentioned, UI virtualization is generally something you don’t have to worry about, as it’s enabled in the controls you’ll generally use for displaying lists: ListView and GridView. Make sure you keep these two things in mind when using ListView and GridView, which can cause UI virtualization to break:

  • When you place either of these panels inside of a ScrollViewer. A ScrollViewer’s behavior tells its child elements to not worry about their height and to take as much space as they need. This will result in the ListView/GridView being completely expanded which breaks the UI virtualization behavior, as all items will be realized.
  • The layout behavior of ListView/GridView can be changed by changing the ItemsPanel Template. The items panel is responsible for the UI Virtualization, so when swapping or modifying that panel, make sure you use panels that support virtualization, such as VirtualizingStackPanel and ItemsWrapGrid, so the list will maintain its UI virtualization behavior.

For more details take a look at the “ListView and GridView data virtualization” documentation on MSDN.

4.      Image rendering optimization

Images can be a large contributor to your app’s memory usage. While it’s often easy to pack high resolution images with your app, or download high resolution images from the web for usage in the app, it can result in poor performance (from both a memory and network perspective). The best way to improve this is to supply images in multiple resolutions and sizes, optimized for the various parts of your app. For example, a list should probably use a small/low resolution image, while a full-bleed background image requires a large/high resolution one.

Decoding your images to an optimal size is also very important for performance. In previous versions of Windows, images were decoded to their natural size, which might not match the actual size on screen, and use extra memory and CPU. This problem was previously addressed by using the DecodePixelWidth/DecodePixelHeight properties to specify the size to decode the image to. Starting with the Spring update of Windows 8.1, this is no longer necessary. Windows will now automatically compute the size the image will occupy on-screen and decode the image to that size.

For customers supplying a stream to the BitmapImage (or BitmapSource), be sure to use the SetSourceAsync method rather than the SetSource method. As its name implies, SetSourceAsync does its decoding asynchronously on background threads, rather than blocking the UI thread, and will save you valuable CPU time.

5.      Ensure your text is on the fast path

With Windows 10, text rendering has also been optimized out-of-the-box, to render up to 50% faster than before. This behavior can be affected by certain properties on a TextBlock, however. To debug this, use the DebugSettings.IsTextPerformanceVisualizationEnabled debug setting in your app. Text that has optimization applied will show up in green:
7_textPerformance

Keep in mind that this overrides the color of the text, so any text that you have defined as being green may appear as a false positive. The XAML for the above example looks like this:

 <TextBlock Style="{StaticResource ScenarioDescriptionTextStyle}" TextWrapping="Wrap" Margin="0,0,0,10" Text="For any text, it [...] snap to.)" /> <TextBlock Style="{StaticResource ScenarioDescriptionTextStyle}" TextWrapping="Wrap" Margin="0,0,0,10">     <Run>         Both word and selection segments are language-specific [...] used.     </Run> </TextBlock> 

As you can see, placing the text in a child Run block instead of directly in the Text property breaks the text rendering optimization behavior. This is true for all inlines including , , , , , , . Some other properties that cause this to happen are:

For more on TextBlock performance, head on over to the documentation on MSDN.

6.      Reduce the size of the Visual Tree

The size of the visual tree in XAML will directly affect both the startup time of the application and its memory usage. Let’s take a look at how we can optimize our app by making sure we don’t create anything in the Visual Tree that we don’t yet need to render.

The easiest way to see your element count is with the Live Visual Tree window in Visual Studio 2015.

8_visualTree
The number to the right of each element is the number of children that element contains.

A common pattern when building Windows apps is MVVM (Model-View-ViewModel), and a very common practice is to use a “Boolean to Visibility” converter to show/hide elements based on properties in the ViewModel. If you’re not familiar with this pattern, this basically allows us to toggle the visibility of items on the page by simply setting a property on it’s associated “controller” class.

While setting the Visibility of elements to Collapsed certainly hides the element from the view, it doesn’t prevent the item from actually being realized. This means that we still spend time in the Layout phase and thus slow down the start-up of our app. As a general rule of thumb, think of every 1000 elements being created in the tree as adding 1 second to your app’s start up time. This may sound like a large number, but a list of items that has a reasonably complex DataTemplate can quickly add up to a multiple of that.

New in Windows 10 is the x:DeferLoadStrategy attribute, which you can use to lazily realize elements when they are first needed. This saves our visual tree from being populated with items that are Collapsed to begin with and we can use these ways to realize them:

  • Call FindName with the name that was defined on the element
  • Call GetTemplateChild with the name that was defined on the element
  • In a VisualState, use a Setter or Storyboard animation that is targeting the deferred element
  • Target the deferred element in any Storyboard
  • Use a binding that targets the deferred element

Going over the places in your XAML where you have a Visibility of Collapsed defined is usually a good strategy to consider places where x:DeferLoadStrategy can be useful.

7.      Data binding optimization

Another thing heavily used when writing XAML apps is data binding. Data binding provides an incredibly powerful and productive way of writing code, especially when combined with the MVVM pattern mentioned earlier. Data binding has been greatly improved with Windows 10 and the way it works has been revamped. Using the new {x:Bind} markup extension for data binding results in compile time code generation for data binding purposes.

 <DataTemplate x:Key="VacationsListViewItemTemplateOptimized" x:DataType="local:IItem">     <TextBlock x:Name="titleTextBlock" Text="{x:Bind Title}" /> </DataTemplate> 

The two changes in data binding markup you’ll need are displayed above. First of all, you need to specify an x:DataType attribute, as the code generation results in strongly typed code. Secondly, use the new {x:Bind} markup extension to do the binding versus the previous {Binding} markup. This also results in code that’s better debuggable, as the data binding code is now in the .g.cs file associated with your XAML page. Be aware that the data context is automatically set to the page itself, so all properties on the page can be bound against.

There are many more improvements made with the new {x:Bind} markup extension, so take a look at the x:Bind sample on GitHub to dive into things like x:Phase, which you can use to specify the order of realization of elements in a DataTemplate when panning lists.

Wrapping up

We hope this article provides you with background and practical guidance on how to approach optimizing your app’s performance. Head on over to DVLUP to take the “Use Visual Studio Profiler to keep your app running at peak performance” quiz to show off your knowledge of XAML performance optimization and claim some coveted points and XP. As usual, there are additional resources listed below and reach out to @WindowsDev on Twitter using #Win10x10 to chime in on the topic.

Resources


Developer Tools Blogs

Leave a Comment