title | description | ms.date | ms.topic | author | ms.author | manager | ms.subservice |
---|---|---|---|---|---|---|---|
CPU profiling in the Performance Profiler |
Learn about the CPU profiler performance tool, which shows the CPU time and percentage spent executing code in C++, C#, Visual Basic, and JavaScript apps. |
08/15/2023 |
how-to |
mikejo5000 |
mikejo |
mijacobs |
debug-diagnostics |
A good way to start investigating performance issues in your app is to understand its CPU utilization using a CPU profiler. The CPU Usage performance tool shows the CPU time and percentage spent executing code in C++, C#/Visual Basic.
The CPU Usage tool can help you:
-
Diagnose a slow-down or a process hang in your team’s codebase. The tool can help you diagnose the issue with your team’s production code. The tool provides automatic insights and various views of your data so that you can analyze and diagnose performance issues.
-
Identify performance issues in DevOps scenarios, such as when a customer reports that some requests or orders are not getting through to the retail website during peak season. Often, the issues are in production, and it is challenging to debug at that moment, but this tool can help you capture enough information and evidence of the issue. After collecting a trace file, the analysis can quickly help you understand potential causes and give suggestions within the context of your code so that you can take the next steps to fix the issue.
-
If your latency issue isn’t within an API request, then you can check for high CPU utilization and other related issues with the CPU Usage tool. The CPU Usage tool can help you identify bottlenecks so that you can narrow down where to optimize.
The CPU Usage tool is helpful for both local trace sessions and production. The CPU Usage tool can also be initiated by using the keyboard shortcut, Alt+F2, and then choosing CPU Usage, or by opening an already collected trace using a tool like dotnet-trace or dotnet-monitor. (For .NET production code, this is most likely how you would collect traces.)
The CPU Usage tool can run on an open Visual Studio project, on an installed Microsoft Store app, or attached to a running app or process. You can run the CPU Usage tool with or without debugging. For more information, see Run profiling tools with or without the debugger.
The following instructions show how to use the CPU Usage tool without the debugger, using the Visual Studio Performance Profiler. The examples use a Release build on a local machine. Release builds provide the best view of actual app performance. For a tutorial that shows how to improve performance using the CPU Usage tool, see Beginner's guide to optimizing code.
Usually, the local machine best replicates installed app execution. To collect data from a remote device, run the app directly on the device, not over a Remote Desktop Connection.
-
In the Visual Studio project, set the solution configuration to Release and select Local Windows Debugger (or Local Machine) as the deployment target.
::: moniker range=">=vs-2022" ::: moniker-end ::: moniker range="<=vs-2019" ::: moniker-end
-
Select Debug > Performance Profiler.
-
Under Available tools, select CPU Usage, and then select Start.
::: moniker range=">=vs-2022" ::: moniker-end ::: moniker range="<=vs-2019" ::: moniker-end
If you enable the Start with collection paused option before starting the profiler, data will not be collected until you select the Record button in the diagnostic session view.
[!NOTE] For more information on how to make the tool more efficient, see Optimizing Profiler settings.
-
After the app starts, the diagnostic session begins and displays CPU utilization data. When you're finished collecting data, select Stop Collection.
::: moniker range=">=vs-2022" ::: moniker-end ::: moniker range="<=vs-2019" ::: moniker-end
The CPU Usage tool analyzes the data and displays the report. If you have trouble collecting or displaying data, see Troubleshoot profiling errors and fix issues.
::: moniker range=">=vs-2022" ::: moniker-end ::: moniker range="<=vs-2019" ::: moniker-end
Use the Filter dropdown to select or deselect threads to display, and use the Search box to search for a specific thread or node.
::: moniker range=">=vs-2022"
To analyze top insights, top functions, and the hot path, see CPU insights. ::: moniker-end
::: moniker range=">=vs-2022" To analyze the CPU Usage report, click Open details, or click one of the top functions to open the Functions view.
The report provides different views of the diagnostic data:
- Caller/callee
- Call tree
- Modules
- Functions
- Flame graph
::: moniker-end
::: moniker range="vs-2019" To analyze the report, click Create detailed report.
The report provides different views of the diagnostic data:
- Caller/callee
- Call tree ::: moniker-end
In all views except Caller/callee, the diagnostic report is sorted by Total CPU, from highest to lowest. Change the sort order or sort column by selecting the column headers. You can double-click on a function that you are interested in, and you will see the source for the function as well as highlighting that shows where time is spent in that function. The table shows columns with data such as the time spent in the function, including called functions (Total CPU), and a second column that shows the time spent in a function, excluding called functions (Self CPU).
This data can help you assess whether the function itself is a performance bottleneck. Determine how much data the method is displaying to see if third-party code or runtime libraries are the reason for your endpoints being slow or resource-consumption heavy.
::: moniker range=">=vs-2022" For more information on using the Flame graph, see Identify hot paths with Flame Graph. ::: moniker-end
To view the call tree, select the parent node in the report. By default, the CPU Usage page opens to the Caller/Callee view. In the Current View dropdown, select Call Tree.
You can click the Expand Hot Path and Show Hot Path buttons to see the function calls that use the highest percentage of the CPU in the call tree view.
::: moniker range=">=vs-2022" ::: moniker-end ::: moniker range="vs-2019" ::: moniker-end
System and framework functions that are executed by your code are called external code. External code functions start and stop the app, draw the UI, control threading, and provide other low-level services to the app. In most cases, you aren't interested in external code, so the CPU Usage call tree gathers the external functions of a user method into one [External Code] node.
::: moniker range=">=vs-2022" To view the call paths of external code, switch the current view to the Call Tree view or right-click and select View in Call Tree.
::: moniker-end ::: moniker range="vs-2019" To view the call paths of external code, on the main diagnostic report page (right pane), select Show External Code from the Filter dropdown, and then select Apply. The Call Tree view of the CPU Usage page then expands the external code calls. (The Filter dropdown is available on the main diagnostic page, not the detailed views.)
::: moniker range=">=vs-2022" Many external code call chains are deeply nested, so the width of the chain can exceed the display width of the Function Name column. The function names then appear as shown in the following image.
::: moniker-end ::: moniker range="vs-2019" Many external code call chains are deeply nested, so the width of the chain can exceed the display width of the Function Name column. The function names then appear as ....
To find a function name you're looking for, use the search box. Hover over the selected line or use the horizontal scroll bar to view the data.
::: moniker range=">=vs-2022" ::: moniker-end ::: moniker range="vs-2019" ::: moniker-end
When the compiler encounters an asynchronous method, it creates a hidden class to control the method's execution. Conceptually, the class is a state machine. The class has compiler-generated functions that asynchronously call the original methods, and the callbacks, scheduler, and iterators needed to run them. When a parent method calls the original method, the compiler removes the method from the execution context of the parent, and runs the hidden class methods in the context of the system and framework code that controls app execution. The asynchronous methods are often, but not always, executed on one or more different threads. This code appears in the CPU Usage call tree as children of the [External Code] node immediately below the top node of the tree.
::: moniker range="vs-2019" In the following example, the first two nodes under [External Code] are the compiler-generated methods of the state machine class. The third node is the call to the original method.
Expand the generated methods to show what's going on:
::: moniker range=">=vs-2022" ::: moniker-end ::: moniker range="vs-2019"
-
MainPage::GetMaxNumberAsyncButton_Click
just manages a list of the task values, computes the maximum of the results, and displays the output. -
MainPage+<GetMaxNumberAsyncButton_Click>d__3::MoveNext
shows you the activity required to schedule and launch the 48 tasks that wrap the call toGetNumberAsync
. -
MainPage::<GetNumberAsync>b__b
shows the activity of the tasks that callGetNumber
. ::: moniker-end
::: moniker range=">=vs-2022"
If you want to view call counts in the Functions view, you can enable the setting before you start the profiler. This setting is supported for .NET project types and requires launching the process under the profiler. The attach scenario is not supported.
-
Select the Settings icon for CPU Usage in the Performance Profiler.
-
Enable the Collect call counts (.NET only) option.
-
Collect CPU usage data.
-
Open the Functions view, and then make sure the Call count column is set to visible.
If you don't see the column, right click a column heading to choose visible columns.
::: moniker-end