left-icon

WPF Debugging and Performance Succinctly®
by Alessandro Del Sole

Previous
Chapter

of
A
A
A

CHAPTER 7

Analyzing the Application Performances

Analyzing the Application Performances


We have seen how the Application Timeline tool provides a convenient way to analyze the behavior of the user interface of your application during its lifecycle. This is particularly important, especially for perceived performance, but an application is not just user interface. You might have a simple UI with code that works with hundreds of objects but that could cause memory leaks, or your application might perform CPU-intensive work. In such cases, analyzing memory allocation and CPU utilization is both useful and important. This chapter provides guidance on built-in profiling tools in Visual Studio that will help you solve problems related to memory allocation and CPU utilization.

Investigating memory allocation

Understanding how the application uses memory is one of the most important steps in building performant applications. Visual Studio provides an analysis tool called Memory Usage you can find in the Diagnostics Hub and that you can see by pressing Alt+F2 (or Debug, Performance Profiler). Figure 38 shows how to enable this tool.

Selecting Memory Usage as the Analysis Tool

  Figure 38: Selecting Memory Usage as the Analysis Tool

Make sure the Release configuration is selected, then click Start to begin a diagnostic session. After a few seconds, you will see that Visual Studio shows the Live Graph and immediately begins reporting the memory usage in MB during the entire application lifecycle (see Figure 39).

Memory Usage Reporting Memory Usage

  Figure 39: Memory Usage Reporting Memory Usage

This kind of report can be useful for determining if memory usage increases or decreases, but the great benefit of this tool is its ability to take snapshots of the memory at a specific point in time. You make this happen by clicking Take Snapshot. Each snapshot contains information about object instances and managed heap size, and, by comparing that shot with a previous snapshot, we can see if the amount of used memory has increased or decreased. Figure 40 shows the result of capturing two snapshots.

Memory Usage Again Reporting Memory Used

  Figure 40: Memory Usage Again Reporting Memory Used

The following is a list of important considerations:

  • The Diagnostic session area reports the session duration and information such as application events and invocations to the Garbage Collector. Figure 40 shows how a garbage collection, which is indicated by a red, triangular mark, has been performed five times. If you hover over a mark with the mouse, you will get a tooltip explaining the reason for the garbage collection. The tooltip is self-explanatory and makes it easier to understand the reason for each garbage collection. Too many garbage collections might be a symptom of bad memory usage.
  • Every snapshot shows the size of the managed heap on the left and the number of allocated objects on the right. In the case of multiple snapshots, they also show the difference between the sizes of the managed heap and between the numbers of object instances.
  • You can click the managed heap size and the list of objects in order to get detailed information, and you can compare snapshots visually.

Let’s discuss this last point in detail.

Investigating the managed heap size

By clicking the managed heap size, you get a representation of the heap size at the time the snapshot was taken, as shown in Figure 41. This view focuses on the order in which objects were allocated rather than their count.

Objects in the Managed Heap at the Moment of a Snapshot

  Figure 41: Objects in the Managed Heap at the Moment of a Snapshot

Among the other things, you can see that there are 306 objects for the ListItem type. Because the application is loading a large number of images in a ListBox control, this is certainly an expected behavior, so we won’t get worried about it. However, this view becomes useful if you have an unexpected, large number of instantiated objects—by knowing the type, it is easier to understand if code is creating unnecessary object instances. Every object can be expanded to get more information about single instances. Notice that the report provides two columns called Size and Inclusive Size. The Size column shows the actual size of the object, whereas Inclusive Size aggregates the object size and the size of children objects. At the bottom of the view, there is a secondary grid called Paths to Root. As the name implies, it shows parent items for the selected object and the reference count. The Referenced Types tab shows you a list of types for which the selected object has a reference.

Analyzing object count

If you go back to the report and click the list of objects within a snapshot, you will get a view that is more focused on the object counts (see Figure 42).

Object Count at Moment of Snapshot

  Figure 42: Object Count at Moment of Snapshot

This view gives you an immediate perception of the number of objects allocated at the time of a snapshot, but the same considerations made in the previous section apply to the objects count view, too. In this specific case, you can see that the number of instantiated objects is consistent with the large number of images the application loads, so this is no surprise. But if you see a type that was unexpectedly instantiated too many times, you have a chance to investigate your code in order to understand where and why it is creating so many instances.

Comparing snapshots

Viewing details of a single snapshot is certainly useful for understanding how memory is used at a specific point of the application lifecycle, but it is perhaps even more useful for understanding if there are more or fewer object instances between two snapshots. The Memory Usage tool provides an option to compare two snapshots very easily. In order to do this, right-click a snapshot, select Compare To, then pick a snapshot from the list that appears. Figure 43 shows the result of the comparison between two snapshots I took for the sample application.

Comparing Snapshots

  Figure 43: Comparing Snapshots

The Count column shows the number of instances of an object in the selected snapshot, while Count Diff. shows the count difference from the other snapshot. Similarly, Size and Total Size Diff. show the current total size of all the instances of an object and the difference from the previous snapshot. Finally, Inclusive Size and Inclusive Size Diff. show the current total size of the instances of an object plus children objects and also show the difference from the previous snapshot. This is probably the most important report you can get with Memory Usage because it allows you to determine if there is a normal or unexpected behavior of your objects at different intervals during the application lifecycle.

Note: Reports generated by any diagnostic tool can be saved for later analysis and comparison. Visual Studio stores reports into .diagsession files that can be reopened later inside the IDE. This is not limited to the Memory Usage tool—it is also true for all the diagnostic tools described in this chapter.

Analyzing CPU utilization

In some situations, you might want to detect where the CPU is spending time executing your code. One of the available tools in the Diagnostics Hub is the CPU Usage. As you can see in Figure 44, this tool makes it easier to analyze the CPU usage in the case of intensive work.

Enabling the CPU Usage Diagnostic Tool

  Figure 44: Enabling the CPU Usage Diagnostic Tool

Before continuing, let’s add some code to the sample application that simulates intensive CPU work. Add the method shown in Code Listing 12, and make sure you invoke such a method after the ImageFileCollection assignment to the Window’s DataContext property. The System.Threading.Thread.SpinWait method causes the running thread to wait for the specified milliseconds, and this is repeated within a 10,000-iterations loop with the purpose of causing CPU overhead.

Code Listing 12

        private void SimulateIntensiveWork()

        {

            var watch = new Stopwatch();

            watch.Start();

            for (int i=0; i < 10000; i++)

            {

                //Simulates intensive processing.

                System.Threading.Thread.SpinWait(800000);

            }

            watch.Stop();

        }

When ready, click Start in the Diagnostic Hub. You will see how Visual Studio starts reporting the CPU usage in the Live Graph. Wait for 30-40 seconds, then stop the diagnostic session. When finished, Visual Studio gives a detailed report, as shown in Figure 45.

Investigating the CPU Usage

  Figure 45: Investigating the CPU Usage

At the top, the report shows the duration of the diagnostic session and the CPU utilization during the application lifecycle. At the bottom, you can see a list of method calls, including constructors and external code, and the CPU usage they caused. In this particular case, the SimulateIntensiveWork method caused the more intensive work for the CPU. The report shows five columns:

  • Total CPU (%), which shows the percentage of usage caused by the selected function and the functions it called.
  • Self CPU (%), which shows the percentage of usage caused by the selected function, excluding the functions it called.
  • Total CPU (ms), which shows the time in milliseconds the CPU was busy because of the selected function and the functions it called.
  • Self CPU (ms), which shows the time in milliseconds the CPU was busy because of the selected function, excluding the functions it called.
  • Module, which shows the component name that contains the selected function or the number of external modules referenced.

As you can easily imagine, the more a function causes the CPU to be busy, the more it should be analyzed in code to see if it is performing expected, extensive work or if a bottleneck has occurred.

Analyzing GPU performances

The Graphics Processing Unit (GPU) is the video card on your machine that makes it possible to render anything you see on screen, from text and windows, to videos and images. In the case of applications that make intensive usage of the GPU, especially with media and games, you can leverage a diagnostic tool called GPU Usage, which is available in the Diagnostics Hub (see Figure 46).

Enabling GPU Usage

  Figure 46: Enabling GPU Usage

This tool’s primary purpose is analyzing applications that heavily use the DirectX graphic libraries. Though WPF invokes DirectX behind the scenes, you will get limited information from GPU Usage unless you code 3-D graphics and animations. However, you can definitely get information about the GPU utilization in any WPF application that works with videos, animations, and, more generally, with media content.

Tip: The goal of this e-book is to cover common WPF scenarios, not specific development contexts such as gaming or 3-D graphics. If you want to see a more specific example of GPU usage with WPF, you can check out the Walkthrough: Hosting Direct3D9 Content in WPF on MSDN.

When you click Start, Visual Studio begins collecting information about the GPU utilization that is immediately reported in the Live Graph. Figure 47 shows a sample report based on a WPF application while playing a video.

GPU Utilization in Action

  Figure 47: GPU Utilization in Action

The Diagnostic session area still reports the duration of the application lifecycle. With DirectX utilization, you can see the frame time in milliseconds and the frame rate per second at a given time. In this case, you only get information in the GPU Utilization area. The utilization is due to the video playing. Before you can analyze details for GPU utilization, you need to select a small interval of time, up to three seconds, then click the view details hyperlink. At this point, you will see a detailed report for the GPU utilization, as shown in Figure 48. If your code uses DirectX directly, you will get details for each marker. In this case, you can see how the GPU has been busy in decoding a video during the selected time frame, and you can see the threads involved in the work. You can hover over each marker to see additional details (if available). At the bottom, there is a list of events; in this case, all events are generically called GPU Work, but would be more detailed in the case of direct calls to the DirectX libraries.

Reporting the GPU Work

  Figure 48: Reporting the GPU Work

Hints about the Performance Wizard

Visual Studio 2015 inherits from its predecessors a profiling tool known as Performance Wizard. This tool has been updated with a modern user interface, but it performs analysis sessions that are essentially similar to the Memory Usage and CPU Utilization, plus has a couple of diagnostic tools that I will describe shortly. Behind the scenes, the Performance Wizard relies on the Visual Studio profiler called VsPerf.exe. This is a command-line tool that can run on machines on which Visual Studio is not installed, such as servers. Performance Wizard is enabled in the Diagnostic Hub, as shown in Figure 49.

Enabling the Performance Wizard

 Figure 49: Enabling the Performance Wizard

The Performance Wizard offers the following diagnostic tools:

  • CPU Sampling, which allows for analyzing the CPU usage.
  • Instrumentation, which measures function call counts and timing.
  • .NET memory allocation, which tracks managed memory allocation.
  • Resource contention data, which is useful for detecting threads waiting for other threads.

I will not discuss CPU Sampling and .NET memory allocation here; you have seen how to use the Memory Usage and CPU Usage, which are more recent tools that target Windows Store apps as well, while the Performance Wizard does not. When you click Start, Visual Studio starts the Performance Wizard by asking you to select the profiling method (see Figure 50).

Specifying the Profiling Method

 Figure 50: Specifying the Profiling Method

Select Instrumentation, which is useful for checking for functions doing the most individual work. When you click Next, you will be asked to specify the analysis target. You can leave the default selection unchanged on the current project, but you could also specify a different .exe file, an ASP.NET application, and even a .dll library. Complete the Wizard and start profiling. While the application is running, Visual Studio collects information about function calls. You can end the diagnostic session simply by closing the application, and you will get a detailed report of the functions doing the most individual work, as shown in Figure 51.

Functions Doing the Most Individual Work

  Figure 51: Functions Doing the Most Individual Work

The Resource contention data tool shows information about thread concurrency—threads waiting for other threads—with most contended resources and most contended threads, as represented in Figure 52.

Understanding Thread Concurrency and Contended Resources

  Figure 52: Understanding Thread Concurrency and Contended Resources

Of course, this tool will be particularly useful with applications that create multiple threads to do some of their work.

The Performance Explorer window

Each time you start a profiling session with the Performance Wizard, Visual Studio collects and organizes reports into a convenient view offered by the Performance Explorer tool window, which should be visible automatically and that you can also enable by selecting Debug, Profiler, Performance Explorer, Show Performance Explorer (see an example in Figure 53).

The Performance Explorer Tool Window

  Figure 53: The Performance Explorer Tool Window

As you can see, Performance Explorer provides a categorized view of the profiling sessions based on their target and profile. You can simply double-click a report file to open the corresponding viewer. You can also compare reports by right-clicking a report name, then selecting Compare Performance Reports.

Chapter summary

Analyzing a WPF application performance is an important task, and Visual Studio demonstrates once again how powerful it is by offering a number of diagnostic tools. With the Memory Usage tool, you can investigate how your application uses memory and have an easier time of discovering memory leaks. With the CPU Usage tool, you can check if your application is performing unexpected CPU-intensive work. With the GPU Usage tool, you can analyze how your application is consuming GPU resources. With the Performance Wizard, you have specialized tools to investigate function calls and thread concurrency.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.