{"id":239033,"date":"2022-08-11T08:00:11","date_gmt":"2022-08-11T15:00:11","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/visualstudio\/?p=239033"},"modified":"2022-08-10T17:46:46","modified_gmt":"2022-08-11T00:46:46","slug":"choosing-a-net-memory-profiler-in-visual-studio-part-1","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/visualstudio\/choosing-a-net-memory-profiler-in-visual-studio-part-1\/","title":{"rendered":"Choosing a .NET Memory Profiler in Visual Studio &#8211; part 1"},"content":{"rendered":"<h2>Summary<\/h2>\n<p>Visual Studio provides two great tools for analyzing and diagnosing memory issues in .NET applications: the <strong>Memory Usage<\/strong> profiler and <strong>.NET Object Allocation Tracking<\/strong> tool. While both tools are useful, it may not be obvious for new user to know which one to use when. This article aims to clarify what each tool is good for and how to use them effectively.<\/p>\n<p>First, let&#8217;s briefly explain what each tool does.<\/p>\n<h2>.NET Object Allocation Tracking tool<\/h2>\n<p>This tool helps you track how many instances of each type are allocated to the heap and their aggregate size and the methods they are allocated from. It helps you answer the questions: Where was this type allocated from? How many instances of this type <u>are<\/u> allocated? Which method accounts for the most allocations? etc. It also collects information about each garbage collection that occurs, such as which types were freed, and which ones survived.<\/p>\n<p>This can be useful when you want to understand allocation patterns in your .NET code. It can also help you optimize your app\u2019s memory usage by tracking down the methods that are most expensive in terms of memory allocations.<\/p>\n<p>The tool shows us where things are allocated from. But it does not tell us why an object is still in memory.<\/p>\n<h2>Memory Usage tool<\/h2>\n<p>This tool allows you to take heap snapshots while your app is running. Each snapshot contains information about objects that were alive at that time and which objects hold references to them. It also displays diffs between different snapshots. This allows you to see what has changed between two points in the app\u2019s execution: which types of objects were garbage collected, which new objects were allocated, etc.<\/p>\n<p>This tool is helpful when investigating memory leaks. It helps you answer questions such as: Which objects were alive on the heap at a certain point in the app\u2019s execution? What are the references keeping this instance alive? Did this object survive a garbage collection run?<\/p>\n<p>However, the tool does not tell you exactly where objects were allocated from.<\/p>\n<p>To illustrate the usage of these tools, we\u2019ll go through some practical examples using sample applications and use cases.<\/p>\n<h2>Note on managed memory<\/h2>\n<p>The tools in this article focus on the managed heap. This is memory that is automatically managed by .NET the garbage collector. When you create a new instance of a class, it gets stored on the managed heap. This contrasts with the unmanaged heap which is not tracked by the garbage collector, it\u2019s mostly used by native code or code that interacts directly with the operating system. Whenever this article mentions the \u201cheap\u201d, it refers to the managed heap. For more information about memory management in .NET <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/automatic-memory-management\">visit this page<\/a>.<\/p>\n<h2>Sample investigation<\/h2>\n<p>In this first example, we have a demo application that stores a list of orders in memory and exposes an API endpoint that returns the total and average sales from orders from a particular region and category.<\/p>\n<p>You can find the full source code <a href=\"https:\/\/github.com\/habbes\/vs-memory-profilers-blogpost\/tree\/master\/CachedOrderStatsSample\">in this GitHub repository<\/a>.<\/p>\n<p>You can clone the repository locally and switch to the <code>CachedOrderStatsSample<\/code> directory to follow along with this section. Inside the repository, navigate to the <code>CachedOrderStatsSample<\/code> directory and open the <code>CachedOrderStatsSample.sln<\/code> in Visual Studio.<\/p>\n<p>The code defines a <code>StatsService<\/code> class which implements a <code>GetStatsByRegionAndCategory()<\/code> method that returns statistics for a specific region and category. The statistics are computed from an in-memory collection of orders that are returned from the <code>DataStore<\/code> class. After computing the statistics for a region-category group the first time, the values are stored in an in-memory cache so that they won\u2019t have to be computed the next time they\u2019re requested. The cache is implemented by the <code>OrderStatsCache<\/code> class, and it uses an object that comprises of the region and category values as key.<\/p>\n<p>Let&#8217;s run the server to test it. Once the server is running, we make the following requests from a web browser or HTTP client like Postman:<\/p>\n<ul>\n<li><a href=\"http:\/\/localhost:5108\/orders\">http:\/\/localhost:5108\/orders<\/a> &#8211; this returns a list of orders<\/li>\n<\/ul>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Sample-request-of-orders-1.png\"><img decoding=\"async\" class=\"alignnone wp-image-239040\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Sample-request-of-orders-1-163x300.png\" alt=\"Sample request returning a list of orders\" width=\"196\" height=\"361\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Sample-request-of-orders-1-163x300.png 163w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Sample-request-of-orders-1.png 516w\" sizes=\"(max-width: 196px) 100vw, 196px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<ul>\n<li><a href=\"http:\/\/localhost:5108\/stats\/Nairobi\/Clothing\">http:\/\/localhost:5108\/stats\/Nairobi\/Clothing<\/a> &#8211; this will return statistics for all the orders made from Nairobi under the Clothing category<\/li>\n<\/ul>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Sample-request-returning-stats-of-orders-in-Clothing-category-in-Nairobi.png\"><img decoding=\"async\" class=\"alignnone size-medium wp-image-239042\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Sample-request-returning-stats-of-orders-in-Clothing-category-in-Nairobi-300x158.png\" alt=\"Sample request returning stats of orders in Clothing category in Nairobi\" width=\"300\" height=\"158\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Sample-request-returning-stats-of-orders-in-Clothing-category-in-Nairobi-300x158.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Sample-request-returning-stats-of-orders-in-Clothing-category-in-Nairobi.png 481w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>But we have received reports that the server\u2019s memory usage grows over time until the server runs out of memory and crashes. We have been asked to investigate this issue.<\/p>\n<p>First, let\u2019s try to reproduce this issue locally to understand what\u2019s happening. Sending a single request to the server might not be enough to observe any strange behavior. There are tools that we can use to send many requests to a server for load testing. For this example, I created a simple Console application for demonstration purposes and included it in the demo repository.<\/p>\n<p>To run the application: while the server is still running, open a terminal window, then navigate to the root of the repository that you cloned, then navigate to the subfolder: <code>CachedOrderStatsSample\/TestClient<\/code> and then run the following command:<\/p>\n<pre class=\"prettyprint\">dotnet run<\/pre>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Terminal-screenshot-of-command-executing-10000-requests.png\"><img decoding=\"async\" class=\"alignnone wp-image-239043\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Terminal-screenshot-of-command-executing-10000-requests-300x19.png\" alt=\"Image Terminal screenshot of command executing 10000 requests\" width=\"742\" height=\"47\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Terminal-screenshot-of-command-executing-10000-requests-300x19.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Terminal-screenshot-of-command-executing-10000-requests-1024x66.png 1024w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Terminal-screenshot-of-command-executing-10000-requests-768x50.png 768w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Terminal-screenshot-of-command-executing-10000-requests-1536x100.png 1536w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Terminal-screenshot-of-command-executing-10000-requests.png 1588w\" sizes=\"(max-width: 742px) 100vw, 742px\" \/><\/a><\/p>\n<p>This sends 10,000 requests to the server.<\/p>\n<p>Now let\u2019s stop the server and proceed to the profiling.<\/p>\n<p>We would like to see how memory usage evolves over time as the application is running, whether it increases indefinitely as reported. This makes the Memory Usage profiler a suitable tool to use.<\/p>\n<p>Before we start profiling, it is recommended to set our project configuration to <strong>Release<\/strong> mode instead of <strong>Debug<\/strong> mode. When an app is built in <strong>Release<\/strong> mode, the compiler performs optimizations that might affect the results reported by profiling tools. Profiling our app in <strong>Release<\/strong> mode therefore leads results which more accurately reflect how our app behaves in production.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Switch-to-Release-Mode.png\"><img decoding=\"async\" class=\"alignnone size-medium wp-image-239046\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Switch-to-Release-Mode-300x100.png\" alt=\"Visual Studio Switch to Release Mode\" width=\"300\" height=\"100\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Switch-to-Release-Mode-300x100.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Switch-to-Release-Mode.png 694w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>To start the <strong>Memory Usage<\/strong> profiler, go to the top menu then click <strong>Debug<\/strong> -&gt; <strong>Performance Profiler<\/strong><\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-Profiler-Menu.png\"><img decoding=\"async\" class=\"alignnone size-medium wp-image-239047\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-Profiler-Menu-300x172.png\" alt=\"Visual Studio Performance Profiler Menu\" width=\"300\" height=\"172\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-Profiler-Menu-300x172.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-Profiler-Menu.png 750w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>This will open a tab that allows us to select the profiling tools we want to run the target application we want to profile. The Visual Studio profiler allows you to profile a running application or launch an executable, but we\u2019re currently just interested in profiling our open project. Make sure the <strong>Analysis Target<\/strong> is set to <strong>Startup Project<\/strong> (<code>OrderCachedStatsSample<\/code>). Then we select <strong>Memory Usage<\/strong>\u00a0from the list of available tools.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-profiler-tools-selector-in-Visual-Studio.png\"><img decoding=\"async\" class=\"alignnone wp-image-239048\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-profiler-tools-selector-in-Visual-Studio-300x114.png\" alt=\"Image Performance profiler tools selector in Visual Studio\" width=\"906\" height=\"344\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-profiler-tools-selector-in-Visual-Studio-300x114.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-profiler-tools-selector-in-Visual-Studio.png 697w\" sizes=\"(max-width: 906px) 100vw, 906px\" \/><\/a><\/p>\n<p>Click the <strong>Start<\/strong> button. This will launch our application with the profiler attached to it. Visual Studio will open a diagnostics session tab that displays real-time information about the memory usage of your app.<\/p>\n<ul>\n<li>Before making any requests, take a snapshot by clicking the \u201cTake Snapshot\u201d button with the camera icon<\/li>\n<li>Run the client tool to send a bunch of requests to the server<\/li>\n<li>Take another snapshot<\/li>\n<li>Send more requests<\/li>\n<li>Take another snapshot<\/li>\n<\/ul>\n<p>Now let\u2019s stop the collection by clicking the <strong>Stop Collection<\/strong>\u00a0button at the top of the diagnostics pane. This will also stop the server.\u00a0 Visual Studio will process the results then present a report like the one below:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Memory-Usage-graph-with-snapshots.png\"><img decoding=\"async\" class=\"alignnone wp-image-239051\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Memory-Usage-graph-with-snapshots-300x85.png\" alt=\"Image Visual Studio Memory Usage graph with snapshots\" width=\"861\" height=\"244\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Memory-Usage-graph-with-snapshots-300x85.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Memory-Usage-graph-with-snapshots-1024x290.png 1024w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Memory-Usage-graph-with-snapshots-768x218.png 768w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Memory-Usage-graph-with-snapshots.png 1048w\" sizes=\"(max-width: 861px) 100vw, 861px\" \/><\/a><\/p>\n<p>At the top, we have a timeline graph that shows how much memory the application used over time. Note that this is the overall memory and not just the managed heap. The blue markers indicate at which points we took snapshots of the heap. If garbage collection occurred, it would be indicated by yellow marks. Nevertheless, taking a snapshot also induces a full garbage collection. Consequently, in our case we had at least 3 garbage collections.<\/p>\n<p>The bottom part displays a table that contains a summary of each snapshot taken. Each row represents a snapshot and contains the following fields:<\/p>\n<ul>\n<li><strong>ID<\/strong>: The identifier of the snapshot<\/li>\n<li><strong>Time<\/strong>: The moment at which the snapshot was taken relative to the start of the profiling session<\/li>\n<li><strong>Objects (Diff)<\/strong>: The number of objects on the managed heap when the snapshot was taken. The number between brackets is the difference between this snapshot and the previous snapshot. A positive number followed by a red upward arrow means there were more objects on the heap than in the previous snapshot. A negative number followed by a green downward arrow means there were fewer objects on the heap.<\/li>\n<li><strong>Heap Size (Diff)<\/strong>: Shows information similar to the <strong>Objects (Diff)<\/strong> column, but this is based on size of the heap rather than a count of objects.<\/li>\n<\/ul>\n<p>Each snapshot allows us to inspect the types of objects on the managed heap at that point. Let\u2019s inspect the objects in the second snapshot. Clicking on the number of objects in the first snapshot will open a new tab that looks like the screenshot below:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Managed-Memory-Snapshot.png\"><img decoding=\"async\" class=\"alignnone wp-image-239053\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Managed-Memory-Snapshot-300x128.png\" alt=\"Image Visual Studio Managed Memory Snapshot\" width=\"888\" height=\"379\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Managed-Memory-Snapshot-300x128.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Visual-Studio-Managed-Memory-Snapshot.png 750w\" sizes=\"(max-width: 888px) 100vw, 888px\" \/><\/a><\/p>\n<p>The main part of the window is a table that contains information about each type of object on the heap. Each row has the following columns:<\/p>\n<ul>\n<li><strong>Object Type<\/strong>: The name of the type or class of the object<\/li>\n<li><strong>Count<\/strong>: The number of instances of that type on the heap<\/li>\n<li><strong>Size (Bytes)<\/strong>: The total size in bytes occupied by instances of this type on the heap (i.e., the size of a single instance multiplied by the number of instances). This does not include the size of objects referenced by these instances.<\/li>\n<li><strong>Inclusive Size (Bytes)<\/strong>: The total size occupied by instances of this type, including the size of objects that they reference. For example, the <code>Node&lt;CacheKey, OrderStats&gt;<\/code> type holds a reference to a <code>CacheKey<\/code> and <code>OrderStats<\/code> objects. And this accounts for all <code>CacheKey<\/code> and <code>OrderStats<\/code> instances in memory. This means that the total inclusive size of <code>Node&lt;CacheKey, OrderStats&gt;<\/code> should be equal to its own (exclusive) size plus the total size of the <code>CacheKey<\/code> and <code>OrderStats<\/code> instances it references. And it adds up: 2,186,600 = 480,000 + 1,066,600 + 640,000<\/li>\n<\/ul>\n<p>For more information about the controls provided by this tool, <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/profiling\/memory-usage-without-debugging2\">visit the documentation<\/a>.<\/p>\n<p><div class=\"alert alert-primary\">There&#8217;s a memory usage profiler that is automatically available in the Diagostics Hub when debugging an application in Visual Studio. It has the added capability of being able individual instances of a specific type in a heap snapshot.<\/div><\/p>\n<p>We see that the top type is a class called <code>CacheKey<\/code>. This is a class defined in our application that\u2019s used as key to store computed statistics data in a <code>ConcurrentDictionary<\/code> that backs our cache. The <code>CacheKey<\/code> is a simple class that contains the properties <code>Region<\/code> and <code>Category<\/code>. <code>OrderStats<\/code> is the class that stores the computed statistics for each region-category group.<\/p>\n<p>Now, click the <code>CacheKey<\/code> row. This will reveal two tabs on the bottom pane: <strong>Paths To Root<\/strong> and <strong>Referenced Types<\/strong>.<\/p>\n<p><strong>Paths To Root<\/strong> is a tree view that displays the chain of objects that have a reference to the type we clicked. If you expand the nodes on the tree, it will eventually lead to a \u201cGC root\u201d. Roots include static fields, local variables on the stack, CPU registers, GC handles and the finalize queue (<a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/garbage-collection\/fundamentals#memory-release\">visit this page<\/a> to learn more). The garbage collector will not free an object so long as there\u2019s a path of references from a root to that object. If an object has no chain of references linking it to a GC root, then it\u2019s considered unreachable, and its memory will be freed by the garbage collector. Therefore, this tree view allows us to see what references are keeping objects alive and is a valuable resource for investigating memory leaks.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Paths-to-root-tree-view.png\"><img decoding=\"async\" class=\"alignnone wp-image-239055\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Paths-to-root-tree-view-300x146.png\" alt=\"Image Paths to root tree view\" width=\"863\" height=\"420\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Paths-to-root-tree-view-300x146.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Paths-to-root-tree-view.png 750w\" sizes=\"(max-width: 863px) 100vw, 863px\" \/><\/a><\/p>\n<p>In the case of the <code>CacheKey<\/code>, all instances are referenced by <code>Node<\/code> objects inside of a <code>ConcurrentDictionary<\/code>. The dictionary is part of our custom <code>OrderStatsCache<\/code>, which is used by the <code>StatsService<\/code> to store statistics. The <code>StartService<\/code> is <a href=\"https:\/\/github.com\/habbes\/vs-memory-profilers-blogpost\/blob\/57c648cd20a06596bc1dc6b5ca201b69e2e8cb30\/CachedOrderStatsSample\/OrderStatsSampleService\/Program.cs#L9\">registered in the dependency injection service container as a singleton<\/a>:<\/p>\n<pre class=\"prettyprint\">builder.Services.AddSingleton&lt;IDataStore, DataStore&gt;();\r\nbuilder.Services.AddSingleton&lt;IOrderStatsCache, OrderStatsCache&gt;();\r\nbuilder.Services.AddSingleton&lt;IStatsService, StatsService&gt;();\r\n<\/pre>\n<p>The <strong>Referenced Types<\/strong> tab shows a tree view that displays all the types that the selected references. It\u2019s basically the inverse of the <strong>Paths To Root<\/strong>. The <code>CacheKey<\/code> has only 2 string properties which account for all its outgoing references:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Object-Referenced-Types.png\"><img decoding=\"async\" class=\"alignnone wp-image-239058\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Object-Referenced-Types-300x31.png\" alt=\"Image Memory Usage Object Referenced Types\" width=\"881\" height=\"91\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Object-Referenced-Types-300x31.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Object-Referenced-Types.png 750w\" sizes=\"(max-width: 881px) 100vw, 881px\" \/><\/a><\/p>\n<p>Registering the cache as a singleton means the instance will remain alive in memory for the entire lifetime of the process. This also means any object it references (directly or indirectly) will also remain in memory. This is expected. And since our application does not implement any eviction logic, it\u2019s also expected that cached items will remain indefinitely in memory. However, that does not explain why we have 10,000 instances each of <code>CacheKey<\/code> and <code>OrderStats<\/code>.<\/p>\n<p>The cache stores statistics by region and category. The orders in our data store only span 3 unique region-category groups. So, we should have at most 3 entries in the cache: 3 instances of <code>CacheKey<\/code> and 3 instances of <code>OrderStats<\/code> regardless of how many requests we make.<\/p>\n<p>After making 10,000 requests, we had 10,000 entries in the cache. It seems like each request inserts a new entry. Let\u2019s look at the final snapshot. If this trend is consistent, then the 10,000 entries should still be in memory, and we should have 10,000 additional entries from the second batch of requests.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Order-Stats-Sample-Snapshot-2.png\"><img decoding=\"async\" class=\"alignnone wp-image-239059\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Order-Stats-Sample-Snapshot-2-300x46.png\" alt=\"Image Memory Usage Order Stats Sample Snapshot 2\" width=\"907\" height=\"139\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Order-Stats-Sample-Snapshot-2-300x46.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Order-Stats-Sample-Snapshot-2.png 750w\" sizes=\"(max-width: 907px) 100vw, 907px\" \/><\/a><\/p>\n<p>As expected, we have 20,000 total instances. We can compare this snapshot with the previous one by clicking the <strong>Compare with Baseline<\/strong> dropdown and selecting the target snapshot ID <strong>(Snapshot #2<\/strong> in this case). This will display a diff report:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Snapshots-Diff-Report.png\"><img decoding=\"async\" class=\"alignnone wp-image-239060\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Snapshots-Diff-Report-300x114.png\" alt=\"Image Memory Usage Snapshots Diff Report\" width=\"897\" height=\"341\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Snapshots-Diff-Report-300x114.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-Snapshots-Diff-Report.png 750w\" sizes=\"(max-width: 897px) 100vw, 897px\" \/><\/a><\/p>\n<p>We can see most of the change is in the types in the cache. They have all increased by 10,000 instances. There\u2019s not been much change in the remaining types, some even have reduced instance count from garbage collection.<\/p>\n<p>We now have a decent idea where the issue comes from. Now we need to dig deeper to figure out the root cause and how to fix it. If we make multiple requests to the same endpoint, e.g., <a href=\"http:\/\/localhost:5018\/stats\/Nairobi\/Clothing\">http:\/\/localhost:5018\/stats\/Nairobi\/Clothing<\/a>, we always get the same response. This is the expected behavior. Let\u2019s look at <a href=\"https:\/\/github.com\/habbes\/vs-memory-profilers-blogpost\/blob\/master\/CachedOrderStatsSample\/OrderStatsSampleService\/StatsService.cs#L14\">the code that handles that request<\/a>:<\/p>\n<pre class=\"prettyprint\">public OrderStats GetOrderStatsByRegionAndCategory(string region, string category)\r\n{\r\n    OrderStats? stats = null;\r\n    if (!cache.TryGetStats(region, category, out stats))\r\n    {\r\n        stats = ComputeStats(region, category);\r\n        if (stats.Count &gt; 0)\r\n        {\r\n            cache.SetStats(region, category, stats);\r\n        }\r\n    }\r\n\r\n    return stats!;\r\n}<\/pre>\n<p>It tries to fetch data from the cache first, and if it doesn\u2019t find it there, it computes the data manually then stores it in the cache. This looks okay. Since our cache is growing, we must be hitting the cache at some point. We might be storing duplicate data. Let\u2019s look at our <a href=\"https:\/\/github.com\/habbes\/vs-memory-profilers-blogpost\/blob\/57c648cd20a06596bc1dc6b5ca201b69e2e8cb30\/CachedOrderStatsSample\/OrderStatsSampleService\/OrderStatsCache.cs#L5\">cache implementation in <code>OrderStatsCache<\/code><\/a>:<\/p>\n<pre class=\"prettyprint\">public class OrderStatsCache : IOrderStatsCache\r\n{\r\n    private readonly ConcurrentDictionary&lt;CacheKey, OrderStats&gt; cache = new();\r\n    public void SetStats(string region, string category, OrderStats stats)\r\n    {\r\n        cache.AddOrUpdate(new CacheKey(region, category), stats, (key, existingStats) =&gt; stats);\r\n    }\r\n\r\n    public bool TryGetStats(string region, string category, out OrderStats? stats)\r\n    {\r\n        return cache.TryGetValue(new CacheKey(region, category), out stats);\r\n    }\r\n\r\n    private class CacheKey\r\n\r\n    {\r\n        public CacheKey(string region, string category)\r\n        {\r\n            Region = region;\r\n            Category = category;\r\n        }\r\n        public string Region { get; }\r\n        public string Category { get; }\r\n\r\n        public override int GetHashCode()\r\n        {\r\n            return HashCode.Combine(Region, Category);\r\n        }\r\n    }\r\n}<\/pre>\n<p>Do you see anything wrong with the code above? In the <code>SetStats<\/code> method, we create a new instance of <code>CacheKey<\/code> from the region and category to update the cache entry. In <code>TryGetStats<\/code> we also create a new <code>CacheKey<\/code> to look up the cache. Two keys with the same region and category should be considered equal and should be matched in the cache. But that\u2019s not what\u2019s happening. Have you seen why?<\/p>\n<p>Well, we have not defined any equality logic for the <code>CacheKey<\/code> class, so the default object equality semantics are used. By default, objects are considered equal only if they refer to the same object in memory. So different instances of <code>CacheKey<\/code> will result in different entries in the cache even if they contain the same data. This is an issue that\u2019s easy to fix. Here are a couple of approaches we can take:<\/p>\n<ul>\n<li>We can implement a custom <code>Equals<\/code> method that overrides the default <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.object.equals\"><code>Object.Equals<\/code><\/a> method<\/li>\n<li>We can define our class as a <code><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/language-reference\/builtin-types\/record\">record<\/a><\/code> instead (C# 9 and later). Records implement value-equality semantics out of the box. The compiler generates custom equality comparison methods that compare each field of the records.<\/li>\n<li>We can define the <code>CacheKey<\/code> as a <code>struct<\/code>. Structs also use value-equality semantics by default. They have the added advantage that they\u2019re allocated on the stack when instantiated, instead of the heap. This may be convenient for small objects that are allocated frequently. Using structs in such scenarios can help reduce the pressure on the garbage collector.<\/li>\n<li>We can define the <code>CacheKey<\/code> as a <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/language-reference\/builtin-types\/record\">record struct<\/a> (C# 10 and later)<\/li>\n<\/ul>\n<p>Before we go ahead and fix the code. Let\u2019s use <strong>.NET Object Allocation Tracking<\/strong> tool to compare the type of reports and insights that we would get from both tools in this scenario. We go back to the <strong>Performance Profiler<\/strong> page, but this time select <strong>.NET Object Allocation Tracking<\/strong> and click <strong>Start<\/strong>.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-Profiler-.NET-Object-Allocation-Tracking-Selected.png\"><img decoding=\"async\" class=\"alignnone wp-image-239063\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-Profiler-.NET-Object-Allocation-Tracking-Selected-300x114.png\" alt=\"Image Performance Profiler NET Object Allocation Tracking Selected\" width=\"1376\" height=\"523\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-Profiler-.NET-Object-Allocation-Tracking-Selected-300x114.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Performance-Profiler-.NET-Object-Allocation-Tracking-Selected.png 750w\" sizes=\"(max-width: 1376px) 100vw, 1376px\" \/><\/a><\/p>\n<p>This will launch the app and a profiling session. Like before, we use the client app to send requests to the server. When we\u2019re done collecting data for the scenario, click the <strong>Stop collection to view .NET Object Allocation data.<\/strong><\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracking-session.png\"><img decoding=\"async\" class=\"alignnone wp-image-239064\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracking-session-300x142.png\" alt=\"Image NET Object Allocation Tracking session\" width=\"1369\" height=\"648\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracking-session-300x142.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracking-session.png 750w\" sizes=\"(max-width: 1369px) 100vw, 1369px\" \/><\/a><\/p>\n<p>This will process the data then display a report like the following:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-report.png\"><img decoding=\"async\" class=\"alignnone wp-image-239065\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-report-300x148.png\" alt=\"Image NET Object Allocation Tracker report\" width=\"1368\" height=\"675\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-report-300x148.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-report.png 750w\" sizes=\"(max-width: 1368px) 100vw, 1368px\" \/><\/a><\/p>\n<p>The top pane has 3 graphs. The <strong>Live Objects<\/strong> chart shows the number of live objects in the heap over time. The second chart shows the percentage increase in the number of objects at different points in time. We can see we have noticeable increases when we make a request then it flattens. The red bar indicates that a garbage collection occurred at that point, between the first and second wave of requests. Notice that this coincides with a drastic drop in the number of live objects. The <strong>Private Bytes (MB)<\/strong> shows how much memory the app uses over time, like what we had in the <strong>Memory Usage<\/strong> report.<\/p>\n<p>This tool does not allow us to take snapshots of the heap. It allows us to see which types were allocated within a specific time range. It can also tell us which types were cleaned up by garbage collection and which ones survived. But it does not tell us which types were on the heap at a specific point in time.<\/p>\n<p>The bottom pane contains tabs with the different tabular report. The <strong>Allocations<\/strong> tab shows how many instances of each type were allocated on the heap.<\/p>\n<p>We see that there were 40,000 instances of <code>CacheKey<\/code> allocated. We allocate a new <code>CacheKey<\/code> in both the <code>SetStats()<\/code> and <code>TryGetStats()<\/code> methods of <code>StatsCache<\/code>. But the instances we create in <code>TryGet()<\/code> get garbage collected. That\u2019s why the <strong>Memory Usage<\/strong> tool only reported 20,000 instances. When you double-click the <code>CacheKey<\/code> entry, it opens a tree view on the right pane that back-traces the methods that allocate that type.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-Allocations-Tab.png\"><img decoding=\"async\" class=\"alignnone wp-image-239066\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-Allocations-Tab-300x69.png\" alt=\"Image NET Object Allocation Tracker Allocations Tab\" width=\"1365\" height=\"314\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-Allocations-Tab-300x69.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-Allocations-Tab.png 750w\" sizes=\"(max-width: 1365px) 100vw, 1365px\" \/><\/a><\/p>\n<p>This information proves to be valuable when we\u2019re trying to track down allocations to reduce memory usage or GC pressure. We will explore more of what this tool offers in the next part of this series. For now, let\u2019s go back to fixing our memory issue.<\/p>\n<p>Let\u2019s go with the simplest fix: turn our class into a Record. We simply change the definition of the <code>CacheKey<\/code> class to the following:<\/p>\n<pre class=\"prettyprint\">private record CacheKey(string Region, string Category);<\/pre>\n<p>Now let\u2019s re-run the <strong>Memory Usage<\/strong> profiler and re-run the same experiment: collecting snapshots after each wave of requests. I got the following report:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-after-fix.png\"><img decoding=\"async\" class=\"alignnone wp-image-239067\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-after-fix-300x61.png\" alt=\"Image Memory Usage after fix\" width=\"1362\" height=\"277\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-after-fix-300x61.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-after-fix.png 750w\" sizes=\"(max-width: 1362px) 100vw, 1362px\" \/><\/a><\/p>\n<p>At first glance, we can see that the number of objects in the last 2 snapshots has significantly reduced from the first report we got earlier. When we open the second snapshot, we see that our <code>CacheKey<\/code> or dictionary <code>Node<\/code> are no longer among the top types:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-with-fewer-cache-keys.png\"><img decoding=\"async\" class=\"alignnone wp-image-239068\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-with-fewer-cache-keys-300x49.png\" alt=\"Image Memory Usage with fewer cache keys\" width=\"1359\" height=\"222\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-with-fewer-cache-keys-300x49.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-with-fewer-cache-keys.png 750w\" sizes=\"(max-width: 1359px) 100vw, 1359px\" \/><\/a><\/p>\n<p>We can use the filter search box to find the field: We only have 3 instances of <code>CacheKey<\/code> and only 3 nodes in the cache. That\u2019s because we only have 3 unique region-category groups:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Manage-Heap-filtered-types.png\"><img decoding=\"async\" class=\"alignnone wp-image-239069\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Manage-Heap-filtered-types-300x38.png\" alt=\"Image Manage Heap filtered types\" width=\"1343\" height=\"170\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Manage-Heap-filtered-types-300x38.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Manage-Heap-filtered-types.png 750w\" sizes=\"(max-width: 1343px) 100vw, 1343px\" \/><\/a><\/p>\n<p>When we look at the diff report of the last snapshot, we see that the number of instances for <code>CacheKey<\/code> has not changed:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Managed-heap-last-snapshot-has-new-cache-keys.png\"><img decoding=\"async\" class=\"alignnone wp-image-239070\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Managed-heap-last-snapshot-has-new-cache-keys-300x34.png\" alt=\"Image Managed heap last snapshot has no new cache keys\" width=\"1359\" height=\"154\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Managed-heap-last-snapshot-has-new-cache-keys-300x34.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Managed-heap-last-snapshot-has-new-cache-keys.png 750w\" sizes=\"(max-width: 1359px) 100vw, 1359px\" \/><\/a><\/p>\n<p>If we collected .NET Object Allocation data, we would not see a difference in the number of allocations for the <code>CacheKey<\/code>. We\u2019re still allocating just as many instances on the heap when we call <code>new CacheKey(\u2026)<\/code>. But most of them get garbage collected. To do away with the heap allocations altogether, we can define the <code>CacheKey<\/code> as a <code>struct<\/code>. The easiest way to do this would be to declare <code>CacheKey<\/code> as a record struct if you&#8217;re usig C# 10 or later:<\/p>\n<pre class=\"prettyprint\">private record struct CacheKey(string Region, string Category);<\/pre>\n<p>If we were to define the struct manually, we could do it as follows:<\/p>\n<pre class=\"prettyprint\">private struct CacheKey : IEquatable&lt;CacheKey&gt;\r\n{\r\n    public CacheKey(string region, string category)\r\n    {\r\n        Region = region;\r\n        Category = category;\r\n    }\r\n\r\n    public string Region { get; }\r\n    public string Category { get; }\r\n\r\n    public bool Equals(CacheKey other)\r\n    {\r\n        return Region == other.Region &amp;&amp; Category == other.Category;\r\n    }\r\n\r\n    public override bool Equals([NotNullWhen(true)] object? other)\r\n    {\r\n        if (other is CacheKey otherKey)\r\n        {\r\n            return Equals(otherKey);\r\n        }\r\n\r\n        return false;\r\n    }\r\n\r\n    public override int GetHashCode()\r\n    {\r\n        return HashCode.Combine(Region, Category);\r\n    }\r\n}<\/pre>\n<p>Implementing <code>IEquatable&lt;CacheKey&gt;<\/code> and custom equality logic improves performance and avoids <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/programming-guide\/types\/boxing-and-unboxing\">boxing<\/a>, which would cause the struct to be allocated on the heap when treated as <code>Object<\/code>.<\/p>\n<p>Now when we re-run our <strong>Memory Usage<\/strong> experiment and look at a snapshot that was taken after making requests, we find that there\u2019s no instance of <code>CacheKey<\/code> on the heap. We still have dictionary <code>Node<\/code> instances on the heap, and the <code>CacheKey<\/code> values are now \u201cembedded\u201d inside the Node instances instead of being stored as separate objects on the heap.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-no-cache-key-on-heap.png\"><img decoding=\"async\" class=\"alignnone wp-image-239071\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-no-cache-key-on-heap-300x38.png\" alt=\"Image Memory Usage no cache key on heap\" width=\"1358\" height=\"172\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-no-cache-key-on-heap-300x38.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/Memory-Usage-no-cache-key-on-heap.png 750w\" sizes=\"(max-width: 1358px) 100vw, 1358px\" \/><\/a><\/p>\n<p>For comparison, let\u2019s collect a report using the <strong>.NET Object Allocation Tracking <\/strong>tool. We find that the <code>CacheKey<\/code> is no longer listed because it has not been allocated to the heap. Calling <code>new CacheKey(\u2026)<\/code> now allocates the instance on the stack, and it&#8217;s automatically freed when the method goes out of scope. If we had not implemented <code>IEquatable&lt;CacheKey&gt;<\/code> we would see many <code>CacheKey<\/code> instances allocated on the heap.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-no-CacheKey-allocations.png\"><img decoding=\"async\" class=\"alignnone wp-image-239072\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-no-CacheKey-allocations-300x104.png\" alt=\"Image NET Object Allocation Tracker no CacheKey allocations\" width=\"1350\" height=\"468\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-no-CacheKey-allocations-300x104.png 300w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2022\/08\/NET-Object-Allocation-Tracker-no-CacheKey-allocations.png 750w\" sizes=\"(max-width: 1350px) 100vw, 1350px\" \/>&#8216;<\/a><\/p>\n<p>If you&#8217;re consider using structs in your own code, I recommend going through the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/design-guidelines\/choosing-between-class-and-struct\">following guidelines<\/a>\u00a0first.<\/p>\n<h2>Conclusion<\/h2>\n<p>We have seen how we can use the Memory Usage profiler to diagnose a memory leak in a sample .NET application. We have also briefly compared the reports returned by the <strong>Memory Usage<\/strong> and <strong>.NET Object Allocation Tracking<\/strong> profilers. The scenario in this demonstration was contrived, the application was simple, and the issue was relatively easy to diagnose and fix. In the real world, the problem will likely be more challenging. Depending on the scenario and the problem being diagnosed, one tool may be sufficient to figure out where the problem is. In other cases, you may need to look at data from different reports and tools to get the full picture of what the problem is. Nevertheless, familiarity with the profiling tools will hopefully make the task easier. In the next part, we will explore more scenarios to better understand where each tool shines.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary Visual Studio provides two great tools for analyzing and diagnosing memory issues in .NET applications: the Memory Usage profiler and .NET Object Allocation Tracking tool. While both tools are useful, it may not be obvious for new user to know which one to use when. This article aims to clarify what each tool is [&hellip;]<\/p>\n","protected":false},"author":20326,"featured_media":255385,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[155],"tags":[9,6743,12],"class_list":["post-239033","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-visual-studio","tag-debug","tag-profiling","tag-visual-studio"],"acf":[],"blog_post_summary":"<p>Summary Visual Studio provides two great tools for analyzing and diagnosing memory issues in .NET applications: the Memory Usage profiler and .NET Object Allocation Tracking tool. While both tools are useful, it may not be obvious for new user to know which one to use when. This article aims to clarify what each tool is [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/239033","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/users\/20326"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/comments?post=239033"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/239033\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media\/255385"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media?parent=239033"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/categories?post=239033"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/tags?post=239033"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}