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
Nobody likes slow or unresponsive apps. Users expect that apps respond immediately to touch, taps, clicks, gestures and key-presses. Users expect that animations are smooth, that they can play, pause and restart their music and videos quickly, and that they never have to wait for the app to catch up with them. This is the first in a series of posts on how to make your apps "fast and fluid."
We invested a lot of time in the engineering teams thinking about how we can ensure the performance of Metro style apps. We have learned what we can do in the platform to deliver on fast and fluid performance and have also learned what works and what does not work in building apps that deliver great experiences. In this blog I share with you some of the hard lessons from our own experiences so that you can build the best possible experiences for your customers.
Performance is more than just a stopwatch and efficient algorithms. When I think of performance, I like to take a holistic view and consider how users experience their time using apps. What does it mean for an app to be fast and fluid? One way to think about it is to separate a user’s experiences into three categories: perception, tolerance, and responsiveness.
Perception contributes to the “fast” in fast and fluid. We define a user’s perception of performance as how favorably they recall the time it took to perform their tasks within your app. In a perfect world a user’s perceptions would match reality. But often this is not the case, and their perception of time matters more than the reality. Have you ever left an installation to finish on its own, and come back to realize it’s paused part way through, waiting for you to answer one more question?
The more steps you can remember about a process, the slower it seems.
Tolerance contributes to both the fast and fluid of fast and fluid. If perception is a measure of a user’s recollection of how much time passed, then tolerance is a measure of how favorable the passing of that time is.
When a user doesn’t know how long an action will take, the wait is painful. Imagine you’re using an app to do some photo editing work. When you click to apply a filter, the app becomes unresponsive. The time it spends frozen quickly becomes intolerable, even if it is just for a few seconds.
Photo apps have solved this problem by adding a progress bar or small animation to indicate that it is working on applying the filter. The amount of time users are willing to wait for the work to complete is far higher because there is no uncertainty. The app is both fast and fluid, and the difference is profound.
Responsiveness contributes to the “fluid” in fast and fluid. If tolerance is a measure of the expectations and favorability of time, then responsiveness is the concept that the expectations of time are relative to the activity being performed. To measure and rate the performance of an activity, there must be a time interval to compare it against. I’ll refer to these time intervals as interaction classes. Internally we use these interaction classes to define responsiveness goals for core scenarios throughout Windows and track failures to meet these goals as bugs in the product.
<= 50 ms
No noticeable delay.
Input response - mouse click, button tap, etc.
50 – 100 ms
Minimally noticeable delay. No feedback necessary.
100 – 300 ms
Quick, but too slow to be described as fast. No feedback necessary.
Open an in-page dialog (i.e. an info tip, pop up, fly out, toast, etc.)
300 – 500 ms
Not fast, but still feels responsive. No feedback necessary.
Navigate to a new page, zooming, display processed or ready data
Medium wait, no longer feels responsive. Not long enough to do something else. May need feedback.
Launch the app, Snap the app, error messages, time-outs, update progress indication
Long enough to do something else while waiting. May need feedback.
Sync or index a device/library
Performance problems show up in various ways. They can reduce battery life, cause panning and scrolling to lag behind the user’s finger, or even make the app appear frozen for an extended period of time. I know that you don’t have infinite time to work on improving performance, so this section will go over some tips on how to identify areas where optimization will have the most impact, and some guidelines that can help you make those optimizations.
One way you can think about making optimizations, is that a process needs to be 20% faster or 20% slower before a user perceives a difference.
You may find "low-hanging fruit" in your app that can greatly increase performance for your key scenarios. But when you fix these issues, it becomes increasingly unlikely to stumble across a single performance issue which will immediately reduce the duration of the process by 20%. More often it is an accumulation of smaller problems along the same path which all contribute to the same user experience issue, and in the next section I discuss a great tool for discovering these paths.
One of the simplest and most readily available techniques for determining where optimizations have the greatest effect is to perform app profiling. Profiling provides data on how much time your app is spending in its various functions, so you can see the hot spots where the app is doing most of its work. Thankfully there is a great profiling tool available in Visual Studio 11 that you can access on the Windows 8 Consumer Preview.
But before you begin, remember that Windows 8 will be run on a wide variety of devices and taking performance measurements on powerful hardware may not truly show performance characteristics on other form factors. For many of my own features, when performing measurements I use a lower-end laptop. For more info see the blog on Running the Consumer Preview: system recommendations.
Here’s how to prep a machine for measurements:
To begin profiling your app:
Two of the most useful views in the performance report are the Call Tree view and the Function Details view. In the Call Tree view, you can see all the functions your app called during execution, how many times they were invoked, and how much time they took to execute. There is also an Expand Hot Path button, which shows you which functions took the longest time to execute. These are the first areas for you to concentrate your optimizations, because they will likely have the largest effect.
The report shows both the inclusive and exclusive times for each function. Exclusive time measures the percentage of time spent executing code in that function. Inclusive time is the percentage of time between when the function was called and when it returned. In other words, it is not only the time spent executing code in the function, but also the time spent executing code in any functions that it calls.
For any of the functions in your app, you can open the Function Details view to get detailed info about what the function did, including the specific code that it executed and what functions it called and how long these functions took. In the example shown in the figure, Array.concat function is the major contributor to the hot path, taking up 29.7% of app execution time. The Function Details view shows us that Array.concat function is actually only taking up about 12.6% of the time itself (shown in orange), and that the majority of the time is actually taken up in get_MainResourceMap function, which the concatenation function calls as part of its execution (shown in purple). You can then click on any of these functions to get more details.
A few weeks before the release of the Windows 8 Consumer Preview, I was using one of the apps and noticed that my laptop was getting hot and the fans started to turn on. Working with the owner of the app, we used profiling to identify some code paths that were causing large amounts of unnecessary CPU usage, which were then fixed. The difference was immediately noticeable, and other app scenarios (such as snapping) were also improved by the changes.
App launch occurs only when your app is not suspended – otherwise your app pops back into view nearly instantly as it is resumed. Keeping your app suspended is a technique for managing perception, tolerance, and responsiveness. Users enjoy your app more quickly, and never experience the uncertainty of waiting for your app to load each time they use it. In short, keeping your app from being terminated is a performance grand slam. The easiest way to do that is to keep your app’s memory usage low when suspended.
Before your app gets suspended, it has a few seconds to do some work. During this time, free any large objects that can be easily re-acquired on resume. This keeps your app’s memory footprint low, and greatly reduces the likelihood that the system terminates your app to make room for something else. Here’s how you can see if your app correctly suspends:
After a few seconds, the word “Suspended” appears next to your app’s name. While suspended, make sure your app’s private working set memory usage is significantly lower than when it is running. I recommend that you target these memory metrics for your app while it is suspended, based on its complexity and general size:
App complexity (approx.)
Private set while suspended (max)
Minimal app (ex. Hello World)
Medium app (ex. Weather)
Large app (ex. Windows Live Photos)
Here’s how you can see how much memory your app is using while suspended:
As disappointing as it may sound, your app is not the only one that will be used :-). Therefore, be a good citizen when running on the user’s system so that they don’t begin to attribute any perceived latencies in the system to your app and start to mentally relate it to bad performance or battery life on their system. Here are a couple tips to help your app play well with others.
The system’s job is to accommodate the resource needs of all Metro style apps by automatically terminating suspended apps to make room for new ones, freeing the user from having to manage resources. A side effect of this is that if an app requests a large amount of memory, then other apps might be terminated– even if the app frees that memory soon after requesting it. To avoid this issue when tackling heavyweight operations, it is best to approach them in small chunks.
Regardless of language, I recommend that you target these runtime memory metrics for your app, based on its complexity and general size:
Total working set (max)
Large app (ex. Photos)
Here’s how you can see how much memory your app is using while running:
One of the biggest concerns for users is the battery life of their devices. Therefore, I recommend that your Metro style apps help conserve energy by freeing their use of system resources when the user is not actively interacting with them. Here are just a few tips to reduce resource usage:
Don’t run animations, audio, or background videos while idle.
Don’t run animations, audio or background videos in an infinite loop.
Don’t perform file caching, device syncing, or any other disk-intensive operation while idle.
In this post I described how you can think about performance, methods for setting goals for the key experiences in your app, and talked about some tools to help identify areas that you might need to optimize. My next post will cover how to go about making these optimizations for the biggest problem areas, and how to avoid some common pitfalls.
I hope these tips will prove useful to you, but if you’re looking for more, feel free to check out our performance best practices documentation.
Until next time,
-- David Tepper, Program Manager, Windows