<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Henrique Ferreira</title>
    <description>The latest articles on DEV Community by Henrique Ferreira (@helfo2).</description>
    <link>https://dev.to/helfo2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1165570%2F59f8de0d-b8a6-44fa-bd3b-874e39ab1157.jpeg</url>
      <title>DEV Community: Henrique Ferreira</title>
      <link>https://dev.to/helfo2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/helfo2"/>
    <language>en</language>
    <item>
      <title>Memory management in C# - Your app probably uses more memory than you think</title>
      <dc:creator>Henrique Ferreira</dc:creator>
      <pubDate>Mon, 18 May 2026 16:51:56 +0000</pubDate>
      <link>https://dev.to/helfo2/memory-management-in-c-part-2-hands-on-52b</link>
      <guid>https://dev.to/helfo2/memory-management-in-c-part-2-hands-on-52b</guid>
      <description>&lt;p&gt;Let's profile a real C# app!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The tooling used in this article is linked at the end. The analysis approach is independent of tooling, but there is a practical bias here towards the Windows platform. I expect you have some familiarity with Visual Studio and the command line, but even without that, you should be able to follow along.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This will be more practical, so please keep in mind that the memory analysis one does is as good as an expected baseline and the experienced delta. In other words, we should try to pick a “control group” (the expected memory consumption) to compare it with a use case (the scenario of analysis, in which the memory consumption seems “too high”, as per some threshold). For example, &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-6.0" rel="noopener noreferrer"&gt;C# ASP.NET Core applications can use from tens up to hundreds of MB at the start in debug mode&lt;/a&gt;. The gist for now is: this is &lt;strong&gt;not&lt;/strong&gt; a micro optimization theoretical exercise - we'll measure an application performance like in the real world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Which app?
&lt;/h2&gt;

&lt;p&gt;We need a long-running process. Let's start by running a simple ASP.NET Core web application and having a look around the memory consumption. &lt;/p&gt;

&lt;p&gt;For that, I've created a folder called &lt;code&gt;complex-app&lt;/code&gt; and ran&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new webapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to download and set up a pre-configured app.&lt;/p&gt;

&lt;p&gt;To run it, we can simply use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or build our project using Visual Studio and run the executable. When assessing performance, prefer the Release build (not Debug), because it will be closer to the realistic production binary. Debug builds carry different symbols we simply &lt;strong&gt;don't need for our purposes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This will start a new .NET Core runtime process with our solution and bind it a to a HTTP port, exposing any available controller routes (the usual stuff, from the &lt;code&gt;Program.cs&lt;/code&gt; entry point).&lt;/p&gt;

&lt;h2&gt;
  
  
  Profiling
&lt;/h2&gt;

&lt;p&gt;To introduce some visualization and look into what's happening memory-wise behind the scenes, let's use &lt;a href="https://github.com/microsoft/perfview" rel="noopener noreferrer"&gt;PerfView&lt;/a&gt;. It's a simple standalone .exe, so I've downloaded it and included it in our Release output folder (in my case &lt;code&gt;complex-app\bin\Release\net10.0&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;For that, we'll run PerfView and select the Collect -&amp;gt; Run option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1noaqzbdnf2u9xgg0u5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1noaqzbdnf2u9xgg0u5.png" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the Command, I'll select our program "complex-app.exe".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxuzgpowsd0riaiuznext.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxuzgpowsd0riaiuznext.png" alt=" " width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And click on "Run command". This is going to start our process from within the Profiler, collecting memory (and trace, and other) data from its execution. We can see the logs of our application by clicking on the "Log" button on the bottom-right corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6t1ub7iy797vdvom3xn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6t1ub7iy797vdvom3xn7.png" alt=" " width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp145lygmbz69da6zc5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp145lygmbz69da6zc5p.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyzing
&lt;/h2&gt;

&lt;p&gt;To analyze our profiling session, all we have to do is click on "Cancel" on the bottom right corner. Then we can get the .etl (analysis) file that was produced as output and open it in PerfView.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Feel free to familiarize yourself with the structure of this file. I invite you to start by double clicking on "CPU Stacks" and selecting the "complex-app" process, like in the following screenshot:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febhm71fj793ndhghr518.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febhm71fj793ndhghr518.png" alt=" " width="799" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Then double click on the "complex-app" process and we'll see what is really consuming time in our program. And the winner is the &lt;code&gt;coreclr&lt;/code&gt;! That's expected, the &lt;a href="https://github.com/dotnet/runtime" rel="noopener noreferrer"&gt;Core Language Runtime&lt;/a&gt; will definitely be doing most of the heavy lifting of our app, in my case representing 36% of total execution time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwlqp0bxrhyk36pvjlhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwlqp0bxrhyk36pvjlhk.png" alt=" " width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: you can drill down by double clicking on the &lt;code&gt;coreclr&lt;/code&gt; reference itself and checking individual method calls. Cool, isn't it? Now we can see the likes of &lt;code&gt;extensions&lt;/code&gt;, &lt;code&gt;aspnetcore&lt;/code&gt;, &lt;code&gt;logging&lt;/code&gt; and much more!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77v5ata7hfsijn7weppa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77v5ata7hfsijn7weppa.png" alt=" " width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at the memory.&lt;/p&gt;

&lt;p&gt;For that, I'll expand the "Memory" folder and navigate to "GC Heap Net Mem", as this is the snapshot of the Garbage Collector. &lt;/p&gt;

&lt;p&gt;So this is how my "complex-app" looks like, there's a bunch of bytes of &lt;code&gt;Int32&lt;/code&gt;, a few of &lt;code&gt;String&lt;/code&gt;... We'll make things more interesting in the next section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbv2s547ln37azjvu1kc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbv2s547ln37azjvu1kc.png" alt=" " width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Load
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A few things to keep in mind before we increase the scale of our app and move to a more relevant analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State of the art CPUs can process billions of instructions per cycle, so ultimately the memory can be allocated, detected in scope and cleared out very fast&lt;/li&gt;
&lt;li&gt;Also due to the complexity of modern applications, when it comes to profiling and optimization we're mostly interested in hot paths and benchmarks (remember the baseline and deltas)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=tD5NrevFtbU" rel="noopener noreferrer"&gt;There is a trade-off between clean code and performance&lt;/a&gt;, arguably that goes all the way down to design discussions between OOP and procedural programming&lt;/li&gt;
&lt;li&gt;Load and scale are very important factors that usually come together: simplistic algorithms perform well with smaller inputs&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's work on some information retrieval functionality for our app. Generally, it's a good practice to think about the data itself: different algorithms behave differently depending on the shape, quantity and quality of our data. Classes vs. structs, primitive vs. complex types, the more we understand about our data, the better. We'll simulate a random list of users with the following user generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;complex_app.Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;complex_app.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserGenerator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt; &lt;span class="n"&gt;_rand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;FirstNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Maria"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Alex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Sophie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Daniel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Lucas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Emma"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Noah"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Isabella"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Liam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Oliver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Ethan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Amelia"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;LastNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Johnson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Brown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Taylor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Anderson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Silva"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Kowalski"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Dubois"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Novak"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Rossi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Fernandez"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;roll&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NextDouble&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roll&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0.70&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;NormalName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0.85&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CaseNoiseName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0.95&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;LongName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;EmailStyleName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;NormalName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirstNames&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LastNames&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;CaseNoiseName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ToRandomCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;NormalName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;LongName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirstNames&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LastNames&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LastNames&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt; III (Contractor - EU West Region)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;EmailStyleName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirstNames&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LastNames&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;@company-internal-domain.eu"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ToRandomCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Simulation is one of the most powerful tools in performance analysis. A late great simulation will never be the same as a memory production issue, but an early good simulation can definitely avoid the issue entirely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we add the list to our app, in &lt;code&gt;Program.cs&lt;/code&gt; - I'll inject it as a global singleton dependency, with 20K users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UserGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can come up with a small search behavior: I have the following interface, with the &lt;code&gt;Search&lt;/code&gt; and &lt;code&gt;SearchFast&lt;/code&gt; methods. You can probably see where we're heading from here, but I promise looking at the memory footprint will be interesting and perhaps can give some unexpected insights even for experienced engineers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;complex_app.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;complex_app.Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IUserSearch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SearchFast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserSearch&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IUserSearch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;UserSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_users&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SearchFast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_users&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Exercising the app
&lt;/h2&gt;

&lt;p&gt;Now what we need to do is simply run our app like before, with the run command. For the sake of empirical analysis, we'll compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A run using only the &lt;code&gt;Search&lt;/code&gt; method, and &lt;/li&gt;
&lt;li&gt;Another run using only the &lt;code&gt;SearchFast&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this experiment, each method will be called 10 times directly through the endpoints &lt;code&gt;GET /seach?q=abc&lt;/code&gt; and &lt;code&gt;GET /search-fast?q=abc&lt;/code&gt;, respectively. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While service warm up is definitely a factor influencing our performance, for this exercise start up times will be aggregated and neglected - for simplicity: both simulate the same piece of behavior with different implementations. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Final results
&lt;/h2&gt;

&lt;p&gt;At last, let's compare using &lt;code&gt;Search&lt;/code&gt; against &lt;code&gt;SearchFast&lt;/code&gt;. So we have two  files open in PerfView, a &lt;code&gt;PerfViewData.etl.zip&lt;/code&gt; and below it a &lt;code&gt;PerfViewData_Fast.etl.zip&lt;/code&gt; (I'm not very good at naming things, as you can see).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupgh73gdoslifvd01kix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupgh73gdoslifvd01kix.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting with CPU stacks, we have our &lt;code&gt;UserSearch&lt;/code&gt; appearing with 3.4% of total CPU time when it comes to &lt;code&gt;Search&lt;/code&gt; (highlighted):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwwagda8eywstqdt22ba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwwagda8eywstqdt22ba.png" alt=" " width="800" height="756"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;SearchFast&lt;/code&gt;, interestingly, the search method stopped appearing in CPU traces entirely. Not because it disappeared, but because it was no longer a meaningful contributor to execution time - its time was below the sampling threshold of PerfView. Why is that the case? Let's look at the GC next.&lt;/p&gt;

&lt;p&gt;We can do an in depth GC analysis by clicking on &lt;code&gt;Memory -&amp;gt; GC Heap Net Mem&lt;/code&gt;. That will open a panel with our GC profile. Looking at &lt;code&gt;Search&lt;/code&gt; first, we see 90% of garbage collection pressure was attributed to string operations. At first glance, it looked like the search logic was the bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffb6vwa1m68bh4hdfg0lh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffb6vwa1m68bh4hdfg0lh.png" alt=" " width="782" height="845"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comparatively, our &lt;code&gt;User&lt;/code&gt; model took 0.8% of GC pressure, coming is as second place.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;SearchFast&lt;/code&gt;, after removing seemingly innocent allocations created by &lt;code&gt;ToLowerInvariant&lt;/code&gt;, string-related GC pressure disappeared entirely. The dominant cost shifted to the user model itself.&lt;/p&gt;

&lt;p&gt;The surprising part wasn’t that performance just improved, it was that the profiler stopped showing strings as a meaningful contributor at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0b6mpp3u06mwdw1hstek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0b6mpp3u06mwdw1hstek.png" alt=" " width="704" height="842"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not only that, we see what looks like, for our purposes, a much healthier application, in which the GC is diving its time between our data models and JSON parsing for the HTTP pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What started as a seemingly simple search implementation ended up revealing something more fundamental about performance in .NET applications: the real cost of a feature is often not where we initially expect it to be.&lt;/p&gt;

&lt;p&gt;At first glance, the Search and SearchFast methods appear almost identical in intent. Both iterate over the same dataset, both perform substring matching, and both return the same result type. From a purely algorithmic perspective, you might even assume their performance characteristics would be similar at this scale.&lt;/p&gt;

&lt;p&gt;However, profiling tells a different story, and that's the importance of understanding about how we leverage memory in practice.&lt;/p&gt;

&lt;p&gt;Commonly in managed runtimes like .NET, the observed slowness or memory leak is actually introduced by a large volume of short-lived allocations. In our example, repeated string transformations, particularly using &lt;code&gt;ToLowerInvariant&lt;/code&gt;. This shifts the workload away from CPU-bound computation and into garbage collection pressure, with the profiler showing strings as the dominant contributor to GC activity.&lt;/p&gt;

&lt;p&gt;In the optimized version, removing these allocations did not simply "improve performance" fundamentally changed the shape of the application. As we saw, string-related GC pressure disappeared entirely from the trace, and the remaining cost shifted naturally toward the data model and pipeline processing.&lt;/p&gt;

&lt;p&gt;This is the key insight: performance work is not always about making a single operation faster. More often, it is about changing what the runtime is actually spending its time and memory on.&lt;/p&gt;

&lt;p&gt;I hope that was useful and see you on the next one :)&lt;/p&gt;

&lt;h2&gt;
  
  
  References and relevant tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-6.0" rel="noopener noreferrer"&gt;Memory management and garbage collection in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sebastienros/memoryleak" rel="noopener noreferrer"&gt;Memory Management and Patterns in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://visualstudio.microsoft.com/" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/perfview" rel="noopener noreferrer"&gt;PerfView&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Memory management in C# - Why we should care</title>
      <dc:creator>Henrique Ferreira</dc:creator>
      <pubDate>Wed, 20 Sep 2023 15:59:57 +0000</pubDate>
      <link>https://dev.to/helfo2/memory-management-in-c-part-1-why-we-should-care-3pjh</link>
      <guid>https://dev.to/helfo2/memory-management-in-c-part-1-why-we-should-care-3pjh</guid>
      <description>&lt;p&gt;One could argue that as software programmers our main goal is to efficiently manage hardware resources. &lt;/p&gt;

&lt;p&gt;There are many of those, like network cards, hard drive disks, SSDs, I/O devices, telemetry devices, and arguably by far most commonly CPUs and RAM modules (notice the different abstraction layers in those examples).&lt;/p&gt;

&lt;p&gt;So this time let’s talk about RAM modules. And C#. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please notice this article greatly simplifies the contents. If you want to expand on the knowledge, please use the references at the end. There are too many great books on the subject by people much more knowledgeable than me :-)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;“Wait, but isn’t it all abstracted away from us, as application developers?” Yes, and here’s why we should still care.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where’s my memory? (a recap)
&lt;/h2&gt;

&lt;p&gt;There are lots of interesting online explanations on Heap vs. Stack memory, especially theoretical and introductory. But how and when should we actually choose to use them? &lt;/p&gt;

&lt;p&gt;Naturally, we are constrained to the &lt;strong&gt;Stack&lt;/strong&gt;. When executing a program, the OS uses a stack implementation. Nothing too fancy, it’s literally (with some simplification) &lt;a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)" rel="noopener noreferrer"&gt;a stack&lt;/a&gt; of function calls (i.e. the Call Stack), internally implemented as a contiguous array of so-called &lt;em&gt;stack frames&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Every language has a calling convention which specifies what goes into this stack (i.e. what is a stack frame). Generally, data is included regarding &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return addresses, for actually JUMPing between function calls&lt;/li&gt;
&lt;li&gt;Parameter data&lt;/li&gt;
&lt;li&gt;Some space for return values&lt;/li&gt;
&lt;li&gt;Scoped variables&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;For our purposes, let’s leave other implementation details to the &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/readme" rel="noopener noreferrer"&gt;C# language specification&lt;/a&gt;, and the lower level stuff for the OS and the likes of Intel and AMD and their Instruction Set Architectures&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From another perspective, the Call Stack tracks where the program is in terms of execution. It has to be a stack basically due to the efficiency of pushing and popping functions in it, which is exactly how a program executes in the first place! &lt;/p&gt;

&lt;p&gt;And there it is, your parameter and variable memory is in “the Stack”. When the function returns, it finishes execution and gets popped from the stack, meaning the memory is automatically free’d up.&lt;/p&gt;

&lt;p&gt;Since C# is a general purpose programming language, we could arguably write any solution with Stack memory only 👀 It’s fast, easy to write, and there’s no need for explicit memory management (the GC will end up managing it as well, but we’ll discuss this in the next sections). Here are some good use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recursive functions&lt;/li&gt;
&lt;li&gt;Procedural programming &lt;/li&gt;
&lt;li&gt;Functional programming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, it is limited. &lt;a href="https://stackoverflow.com/questions/28656872/why-is-stack-size-in-c-sharp-exactly-1-mb" rel="noopener noreferrer"&gt;The C# (.NET) stack size for 32bit architectures defaults to 1MB per Thread&lt;/a&gt;, which is a lot for most applications - we can go tens of thousands of levels deep for simple recursive functions. If you exceed it, be prepared for an infamous &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.stackoverflowexception?view=net-7.0" rel="noopener noreferrer"&gt;StackOverflow exception&lt;/a&gt; (that said please don't be afraid of recursive code).&lt;/p&gt;

&lt;p&gt;The other problem is scope, meaning we’d have to pass down local variables as parameters all the way through the code execution, which would make complex code less readable and a nightmare to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should it be all “new”?
&lt;/h2&gt;

&lt;p&gt;But don’t panic! (and make sure to bring your towel). We still have the &lt;strong&gt;Heap&lt;/strong&gt;. As with the Stack, it also resides within RAM. But it's a different data structure, for a different kind of access.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Heap_(data_structure)" rel="noopener noreferrer"&gt;A heap&lt;/a&gt; can be implemented just as well from a simple array. The rules are different though, since it is a tree-like structure that allows for some optimized read access at the cost of more complex writes. So yes, it is slower than the Stack (it can even end up with some fragmentation). So why use it? It's virtually limitless! We can easily throw data at it, and we get the reference to where that data is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As opposed to a &lt;em&gt;value&lt;/em&gt; which holds the value itself (&lt;code&gt;int x = 2&lt;/code&gt;), a &lt;em&gt;reference&lt;/em&gt; is a pointer which holds an address to where the value is dynamically allocated (&lt;code&gt;var x = new string[10]&lt;/code&gt;). The address size can vary, but on an 32-bit machine, they are usually 4 bytes long (pointing to anything in the Heap, including functions!). The difference is commonly abstracted away from the programmer in C#, so it is a good idea to have the &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types" rel="noopener noreferrer"&gt;C# Value type docs&lt;/a&gt; and the &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/reference-types" rel="noopener noreferrer"&gt;C# reference types docs&lt;/a&gt; close in case of doubts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So how do we use the heap in C#? The short, over-simplified and slightly off answer is with the &lt;code&gt;new&lt;/code&gt; keyword (or instantiations). The complete answer is: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;em&gt;reference&lt;/em&gt; type is allocated on the heap&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;value&lt;/em&gt; type is created where the scope is: if it's a field in a class, along in the heap. If it's a local variable in a method, most likely on the stack (notice C# has tons of features and there are more complex scenarios, for lambda expressions there have shared scopes for example)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above list is short and ambiguous on purpose. The language specification makes no guarantees on what goes where, and the compiler will ultimately and optimally decide. &lt;/p&gt;

&lt;p&gt;To another extent, the C# heap is a global &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/automatic-memory-management" rel="noopener noreferrer"&gt;Managed Heap&lt;/a&gt;. That also means there is a runtime data structure running along with our code: the Garbage Collector.&lt;/p&gt;

&lt;h2&gt;
  
  
  So my code &lt;del&gt;is&lt;/del&gt; produces Garbage?
&lt;/h2&gt;

&lt;p&gt;Jokes apart, the &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals" rel="noopener noreferrer"&gt;Garbage Collector (GC)&lt;/a&gt; doesn't mean to offend us. It is responsible for effectively managing the C# memory (Heap and Stack), and can be our best friend when used correctly.&lt;/p&gt;

&lt;p&gt;To reiterate, the GC runs &lt;strong&gt;with&lt;/strong&gt; our code and goes reclaiming dead objects (pieces of memory we're not using). Hence we don’t need to explicitly free memory, which is a great and dangerous power. Most content out there will tell you to leave it all to the GC, since it knows better.&lt;/p&gt;

&lt;p&gt;If you’ve gone this far though, you’re like me and is not particularly happy with having some data structure we can’t control taking care of so much of our program. So like Morpheus, I’m hereby admittedly inviting you to take the red pill with me. &lt;/p&gt;

&lt;h2&gt;
  
  
  Collecting the garbage
&lt;/h2&gt;

&lt;p&gt;Remember the &lt;em&gt;reference&lt;/em&gt; type from the previous sections? Let’s assume the OOP nature of C# and start calling it “object” (it has to be an object anyways given how an OOP compiler makes Semantic Analysis).&lt;/p&gt;

&lt;p&gt;In a “managed” language like C#, objects have lifetimes: a short-lived object has been allocated recently and is likely closed related to others also just allocated, while a long-lived is older and maybe less related.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For completeness sake, the GC also differentiates between Large Objects (85,000 bytes and larger) and regular ones, with a separate heap for each type&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The criteria to define an object lifespan is based on so-called &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#generations" rel="noopener noreferrer"&gt;GC generations&lt;/a&gt;. There are three of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gen 0&lt;/strong&gt;: consists of youngest objects, like a recently allocated temporary variable. It’s the cheapest to manage, being the first option to free space for new allocations and also where GC collections are more frequent. All new objects implicitly go to Gen 0, except Large Objects, which go straight to Gen 3. &lt;em&gt;We like Gen 0&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gen 1&lt;/strong&gt;: literally the mid way of object lifetimes, in between short and long-lived objects. After the Gen 0 collection, the memory is compacted and objects - like collections, for example - are promoted to Gen 1. Gen 1 is not checked every time for collection, only when the Gen 0 collection couldn’t feee up enough space for newer allocations. &lt;em&gt;We have mixed feelings for Gen 1.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gen 2&lt;/strong&gt;: long-lives objects, usually alive for the duration of a process (like Singletons, for example). Surviving a Gen 2 collection (i.e. a full collection) means sticking around until the memory is determined unreachable. &lt;em&gt;We’re not fans of Gen 2, but it’s serves its purpose&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gen 3&lt;/strong&gt;: is just a nickname space for Large Objects only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Objects can survive collections, being promoted from Gen 0 to Gen 1 and Gen 1 to Gen 2. In simples terms, the GC will try to find the sweet spot between not getting too intrusive in collections and not letting the memory become too big.&lt;/p&gt;

&lt;h2&gt;
  
  
  GC - Ghost Component
&lt;/h2&gt;

&lt;p&gt;What we’ll discuss now may seem like a lot of developer work for a GC that automatically sorts out the memory usage. It isn’t. The thing is the GC makes thread pauses (for ephemeral collections - Gen 0 and Gen 1 - lasting a couple of milliseconds) to actually cleanup the memory. In general, what we want is to &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/performance#issue-an-out-of-memory-exception-is-thrown" rel="noopener noreferrer"&gt;make these pauses less frequent and faster&lt;/a&gt;. Most of the real challenge goes to understanding how have we implemented the solution - how we have used the memory.&lt;/p&gt;

&lt;p&gt;So you may have heard about “problems with the GC” or “memory leaks in C#”. These are usually due to Gen 2 objects that are hanged around or maybe a concentration of Large Objects.&lt;/p&gt;

&lt;p&gt;Some objects interact with “out-of-process” resources (i.e. unmanaged resources), like disk I/O, and network connections (and any other general OS resources). When that happens, explicit cleanup becomes necessary (after all we wouldn’t want the GC to step in and shut down a network port, and it wouldn’t know if the port is being used anyways).&lt;/p&gt;

&lt;p&gt;With regards to such unmanaged resources, a case-by-case analysis is necessary. For the GC to be able to collect them, we should implement the &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/using-objects" rel="noopener noreferrer"&gt;IDisposable.Dispose method&lt;/a&gt;. We should also consider using destructors, or in C# terms, the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.object.finalize?view=net-7.0" rel="noopener noreferrer"&gt;Object.Finalize method&lt;/a&gt; in case there’s a chance of &lt;code&gt;Dispose&lt;/code&gt; not being called by a developer.&lt;/p&gt;

&lt;p&gt;Finally, if you make too many allocations, you may get a nasty &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.outofmemoryexception?view=net-7.0" rel="noopener noreferrer"&gt;OutOfMemory exception&lt;/a&gt;. That ultimately means the &lt;a href="https://learn.microsoft.com/en-nz/archive/blogs/ericlippert/out-of-memory-does-not-refer-to-physical-memory" rel="noopener noreferrer"&gt;OS was not able to provide addressable space for allocations&lt;/a&gt;. In such a case, remember: the .NET CLR, .exe and other .dll modules are sharing the memory with your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyzing and profiling
&lt;/h2&gt;

&lt;p&gt;In the next parts, we’ll actually analyze and profile a C# program in practice. Then, we’ll discuss tools, trade-offs and myths in terms of memory management. See you there!&lt;/p&gt;

&lt;p&gt;PS.: Please leave your comments and feedback. This very condensed article should be used as a general discussion and not a static reference. Let me know if I should expand on any of the content :)&lt;/p&gt;

</description>
      <category>programming</category>
      <category>csharp</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
