posting this on behalf of Jonathan Carter who authored this post
Performance is a critical aspect of any 5-star rated app. Users expect pages to load quickly, interactions to respond immediately and animations to be fluid. While it’s easy to recognize the importance of these qualities, achieving them can often prove challenging, especially with the tight demands they impose (e.g. 60 FPS requires <16ms per frame).
This post introduces the HTML UI Responsiveness tool, a profiler within the new Performance and Diagnostics hub, that enables you to improve the performance of your Windows Store apps using JavaScript by providing a semantically-rich, Web Platform-centric view of CPU utilization.
What is the value in having a new profiler for JavaScript apps?
The key to maintaining a responsive app is keeping the UI thread as free as possible. This way, it will be ready to respond to user-interaction (e.g. button click, scrolling) as soon as it occurs and will be able to maintain a fast and consistent frequency when producing new frames within an animation/game.
If a performance bottleneck is caused by JavaScript code executing too long, the existing JavaScript Profiler can help identify the exact function which is to blame and allow you to make the appropriate optimization(s). However, bottlenecks can occur from many other sources, because the UI thread is responsible for handling much more than just running JavaScript callbacks. In fact, nearly every part of the Web Platform pipeline executes on the UI thread, including the following behaviors:
- Parsing HTML content and constructing the respective DOM elements
- Parsing CSS content
- Determining the style properties for each DOM element (style cascading)
- Determining element size/position (layout)
- Determining which objects are no longer needed and freeing their associated memory (garbage collection)
- Rendering new content to the screen (IE11 adds support for “independent rendering” that moves this step to another thread)
In order to fully optimize UI thread workload, and therefore app responsiveness, you need visibility into your app’s holistic CPU utilization, which includes not just JavaScript execution, but also the critical platform-provided “services” that occur as a result of your app logic (e.g. GC running due to lots of object churn, layout running due to modifying the width of an element).
This visibility is exactly what the HTML UI Responsiveness tool provides and is what makes it appropriate as the “first-line of defense” for investigating issues with app responsiveness and rendering fluidity within Windows Store apps using JavaScript.
Getting started with the HTML UI Responsiveness tool
The entry point for starting the HTML UI Responsiveness tool can be found within the launch page of the Performance and Diagnostics hub (which can be opened via the “Analyze -> Performance and Diagnostics” menu item). If your startup project is a Windows Store app using JavaScript, you’ll notice that HTML UI Responsiveness is listed amongst the available tools for this target type.
You can begin a new profiling session by selecting the checkbox and clicking the Start button. At this point, you can return to your app and exercise the scenario you’d like to investigate. When completed, return to Visual Studio and click the Stop collection link (or Stop button) to end your profiling session and begin analyzing the collected data.
Once the analysis has completed, you’ll be presented with a performance report that you can begin investigating for optimization opportunities.
The tool presents you with a sequential timeline that indicates when your app utilized CPU, and more importantly, what it was doing during that time. This makes it possible to spot periods of excessive CPU usage (which could lead to unresponsiveness) and map it to the offending “events”. Because the tool is optimized for apps written in JavaScript, these “events” are labeled and described in terms which are familiar to the Web Platform (e.g. “DOM event”, “Layout”, “HTML parsing”), which makes it easier to correlate CPU utilization to logical/intuitive operations (e.g. my app was running at this point because a 5s timer expired, which triggered the checkForUpdates function, which in turn made an HTTP request, and added three new
Additionally, the timeline also indicates the frame rate that your app was achieving throughout the duration of the session, which makes it easier to understand how CPU utilization was affecting the ability to maintain a consistent visual throughput. This makes it possible to spot “dips” in your frame rate and correlate them to the offending operations.
Interacting with the timeline details
Once you’ve identified a region of interest within the session (based on examining either the CPU utilization and/or Visual throughput graphs), you can investigate that period of time by click-and-dragging that selection on either of the graphs (or the ruler). As you change your selection, the Timeline details will automatically update to reflect this filtered view. If you need to adjust your selection to include more or less time, you can select the handles on the ruler and use the left/right arrow keys to achieve more fine-grained control.
The Timeline details is where your primary investigation will occur. It displays a detailed view of CPU utilization that is categorized/colored by subsystem (e.g. resource loading, JavaScript, styling) and labeled by operation type (e.g. DOM event, Timer, Layout). Most of the events occur on the UI thread, but for events that don’t (e.g. image decoding always happens on a background thread), the name of the event will include a thread ID, which simply helps distinguish that the event did in fact occur on a background thread.
When you find an event of interest, you can further investigate it using the following gestures:
- Selecting the event row, which will display additional details about it in the detail pane (the area to the right of the Gantt chart). For many events, this extra data assists in identifying the event and understanding why it occurred (e.g. the type of event, the elapsed time of a timer)
- Expanding the event row (if applicable), which will display the “child” events it synchronously triggered (e.g. a DOM event triggering HTML parsing due to setting innerHTML on a DOM element)
o Because an event can trigger other events, the tool supports the notion of “Inclusive” vs. “Exclusive” time, which allows you to identify whether an event was expensive because it was churning on its own work (e.g. long CSS parsing due to a huge stylesheet) or because it instigated additional work and was waiting for it to complete (e.g. a requestAnimationFrame callback that had very little JavaScript but triggered inline layout due to a call to getComputedStyle)
By default, the timeline is sorted sequentially, which means that events that occurred earlier in time will appear higher (and to the left) than events which occurred later in the current selection. If you’re interested in identifying the most expensive events (based on inclusive duration), you can change the Sort by option to “Duration (inclusive)” and the timeline will immediately bubble up the “hot paths” that contributed the most to your bottleneck.
There are many event types, lots of operation metadata and numerous combinations of parent/child relationships (e.g. HTML parsing triggering Script evaluation, a DOM operation triggering Paint, Timer triggering CSS parsing). The data can be vast, but this expressive profiling information is what makes the tool so powerful and allows you to make informed optimizations.
To illustrate the types of rich data/patterns that the tool surfaces, the following are some unique scenarios that it allows you to identify:
- Sub-resources loaded by your app (e.g. , ), along with their individual parsing/evaluation costs, which make it easier to optimize page load times
Example showing the set of JavaScript files loaded as a result of the parser
2. Event-driven causality around why script is running (e.g. event handler, timer, requestAnimationFrame callback), along with the identifiable metadata about the event, and source navigation capabilities, which make it easier to optimize user-interaction
Example showing the target element, callback function name and source location of a specific DOM event
3. DOM calls made within script, along with their synchronous rendering side-effects (if any)
Example showing how calling getComputedStyle on the specific 4. DOM elements that were affected in a specific layout/style calculation/paint pass, along with the exclusive time they contributed to the operation, which makes it easier to optimize rendering costs Example showing the set of elements that required their layout to be re-computed 5. Much much more! In order to make a profiling session efficient and actionable, you want to scope down the area of investigation as far as possible. This way, you’re not trying to interpret data that doesn’t actually relate to the problem you’re trying to solve. Because this capability is so critical to the typical performance analysis workflow, the HTML UI Responsiveness tool allows you to instrument your app with “milestones of interest”/scenario boundaries which can significantly help provide the context needed to decide where in the trace you should focus. To make use of these markers, you simply add calls to the performance.mark method at the relevant locations within your codebase. When you subsequently profile the app, these calls will show up as marks (the legend refers to them as “User marks” because their app-specific) on the ruler. With your scenario-specific marks in place, you can easily identify the period of interest on the graphs, select its timespan and begin an isolated investigation. Additionally, the ruler within the Timeline details also display your marks, which allows you to see the exact events which occurred during the problematic period you’ve selected. To help bootstrap your investigations in lieu of having any app-specific markers (or supplement your custom marks), the tool also displays ruler marks which represent fundamental events that occured during a profiling session (called “App lifecycle events”). The following event types are currently supported: In some cases, these marks alone can help isolate specific scenarios. For example, being able to identify the period of time between a navigation and the subsequent DOMContentLoaded event would allow you to investigate the cost of sub-resources being loaded and how they individually affect page load time. The HTML UI Responsiveness tool provides a lot of power and visibility into data that was otherwise difficult to ascertain. We hope this allows you to make more informed optimizations regarding your app’s performance, which will ultimately contribute to its success. We’ll be posting subsequent articles which describe performance patterns to look for in the tool, as well as how to resolve them in your code. In the meantime, you can see the tool in action in the following //build 2013 talk: “Developing High Performance Websites and Modern Apps with JavaScript Performance Tools”. Most importantly, however, we want to hear from you! If you get a chance to evaluate the tool, please let us know how your experience went, as well as any feedback/suggestions/complaints you may have. You can reach out by means of UserVoice, Connect, Twitter (mention “HTML UI Responsiveness”), or by asking questions in the MSDN diagnostics forum.Measuring an isolated scenario
We want to hear from you!