WinRT/XAML Performance Fundamentals
The three Pillars of Performance, Fast, Fluid, and Efficient, have been the common theme at the performance sessions this year. In the session XAML Performance Fundamentals we see ways to detect and solve problems that prevent us from achieving these pillars.
The session begins with these precepts:
Know your runtime: Direct2D, Direct3D, DirectComposition, etc. offer the fastest runtime performance but don’t support accessibility, IME, input handling, and the like. Conversely, high level frameworks like HTML and XAML have powerful features but come with a performance cost.
Know your budget: If you don’t know how much time you can afford to spend in a given operation you can’t make meaningful design decisions. For example, on the UI thread you only get 16.6 milliseconds before you start dropping frames. From that budget you have to pay for layout and rendering, data binding, and application code.
Known you patterns: Using patterns (like MVVM) can make developers more productive but they typically don’t make the application user’s life better, especially when they add layers that slow down the app. If the patterns prevent you from staying in your budget you must reconsider them.
The XAML Architecture is inherently multi-threaded. There are two primary threads and possible one or more background threads. Their duties are:
UI Thread (i.e. main thread)
- App Code
- Data binding
- Input Processing
- Submitting Frames
- Running Animations
- Submitting Frames
- Image Decoding
- Text Rendering
- Media Playback
Application Startup the following operations need to be performed on the UI thread.
- Layout (measure pass and arrange pass)
- ApplyTemplate (often triggered by the measure pass)
- Data binding
- Input processing
- App code
- Render pass: drawing instructions sent to DirectComposition
Meanwhile the render thread is setting up animation loops. If so, it sends updates to DirectComposition. Finally there is the Desktop Window Manager, which composes frames and presents them to the screen.
Improving Application Startup
Knowing the operations that occur during startup, these recommendations for improving startup performance are fairly obvious.
- Reducing the number of XAML files.
- Trim down your element count.
- Use UI virtualization to create only visible items
- Delay load UI elements, especially when they are in tabs that aren’t visible yet
- Collapsed user controls should be avoided. While they don’t need to be rendered, their XAML files still have to be parsed. Custom controls that don’t have associated XAML are acceptable.
Looking at Performance Metrics
The primary tool for analyzing XAML applications is the Windows Performance Analyzer. It is a fairly complicated product, so WPA ships with a profile specifically created to show graphs relative to XAML-based applications.
This tool allows you to see what happens in each frame. When you find a frame that takes too long to render, you can use this to gather important metrics like how many XAML files were parsed, how many UI elements were created, and how much time was spent on layout and rendering.
Summarizing the purpose of these graphs is beyond the scope of this news report. We recommend you view the XAML Performance Fundamentals video for more information.
Animations can be dependent or independent. A dependent animation is processed entirely on the UI thread while independent animations are mostly processed by the Render thread. Dependent animations are universally considered to be bad.
Another problem with animations is the way they can overwrite regions multiple times. With Windows 8.1 you can turn on DebugSettings.EnableRedrawRegions to see where hidden animations are consuming CPU resources. A common example of this would be a waiting indicator that is not visible but yet its animation is still running.
A similar property, EnableOverrun, will paint in red the regions that are being overwritten. The deeper the red color, the more times the regions was overwritten.
When panning something, say a list control, the operation differs depending on whether or not the panel is virtualized. If it isn’t virtualized, then everything occurs on the render thread. This makes panning really fast, but the setup costs are quite expensive.
If the panel is virtualized, the UI thread will attempt to build the objects in real time to fill in the blank spaces left behind the moved elements. Some extra elements are kept by the virtualizing panel so that it doesn’t always need to create the elements from scratch. But those elements will still need to be moved into place and data binding has to be reapplied.
To improve panning consider reducing the number of UI elements low, watch for excessive data binding, and carefully evaluate nested templates.
If you still don’t get the performance you want, you can try using the ContainerContentChanging event in Windows 8.1. This allows you to incrementally render the content of a panel. For example, you could show just image placeholders at first. When the user stops panning you can go back and render the interactive elements you actually wanted.
When using media playback, try to avoid overlaying media and XAML elements. If possible, run the media full screen so that the media infrastructure and completely take over rendering.
Improvements in WinRT 8.1 over 8.0
A lot of work has been put into improving the WinRT/XAML runtime. These include:
- Faster startup
- Default templates in Visual Studio following better practices
- Support for binary XAML
- Faster Data Binding
- Reduced memory footprint
- Placeholders and incremental loading for ListViewItems
- Improved graphics performance
- Improved media player integration
None of these improvements were back-ported to WPF or Silverlight. At this point is looks as if both technologies are in maintenance mode.
This information was originally presented in the Build 2013 session XAML Performance Fundamentals.
Camille Fournier May 21, 2015