<?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%2Feec5ae54-a8cb-48ab-b607-b892280f4ea3.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# - Part 2: analyzing</title>
      <dc:creator>Henrique Ferreira</dc:creator>
      <pubDate>Wed, 04 Oct 2023 21:09:02 +0000</pubDate>
      <link>https://dev.to/helfo2/memory-management-in-c-part-2-analyzing-and-profiling-3di3</link>
      <guid>https://dev.to/helfo2/memory-management-in-c-part-2-analyzing-and-profiling-3di3</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/helfo2/memory-management-in-c-part-1-why-we-should-care-3pjh"&gt;Part 1: why we should care&lt;/a&gt;, we've revisited some concepts, discussing the theory with a more practical point of view. In this Part 2, we'll focus on getting our hands dirty (with some theory to back us up, I'm afraid) to actually profile and understand the C# memory usage in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tooling
&lt;/h2&gt;

&lt;p&gt;There are many free and paid tools to statically analyze a code base, profile it's memory, CPU usage, code coverage, assembly generation, and many more Software Engineering metrics. They'll maybe generate graphs and charts, and overall many numbers as results. &lt;/p&gt;

&lt;p&gt;Quantitative and qualitative analysis is a complex subject that goes beyond Software Engineering. In summary, none of the numbers should matter, not more than the meaning behind them. Also, although paid tools may be more complete, there's already a lot of value to be gained with the free tools, which is what we'll use, of course (as I'm poor).&lt;/p&gt;

&lt;p&gt;The basis of our discussion is that lot of the memory a program uses is due to the applied solution. Being a popular OOP language, C# commercial programs will usually consist of the use of a framework (such as .NET's CLR - like the &lt;a href="https://github.com/dotnet/core" rel="noopener noreferrer"&gt;open sourced .NET Core&lt;/a&gt; or the &lt;a href="https://referencesource.microsoft.com/" rel="noopener noreferrer"&gt;at least very well referenced .NET Framework&lt;/a&gt;), third party libraries (at the moment, there are over 370k of them in &lt;a href="https://www.nuget.org/" rel="noopener noreferrer"&gt;Nuget.org&lt;/a&gt; only) and our own code, with its .dlls. From any of these places, we should know that human beings - smart, but prone to failures - made decisions on how algorithms and data structures should behave in OOP to solve particular issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real (but theoretical) analysis
&lt;/h2&gt;

&lt;p&gt;Therefore the first tool we should use is our ability to understand the said human decisions by looking at the code. And a very big part of it is with algorithms and data structures analysis. It follows that the most comprehensive strategy (obviously it can be used for any programming language) relies on &lt;a href="https://en.m.wikipedia.org/wiki/Asymptotic_computational_complexity" rel="noopener noreferrer"&gt;asymptotic computational complexity&lt;/a&gt;. Although the terminology is fancy, we can easily bring it to some triviality - the question one wants to answer in terms of engineering by looking at code is: what does this cost (in the worst case scenario)?&lt;/p&gt;

&lt;p&gt;The cost is usually measured in CPU time and memory consumption. Luckily for us, we don’t need to know every CPU clock rate out there (not even the compiled instructions 😀) and it’s also dispensable to know the RAM memory chip’s hardware speed. We use a slightly different RAM, the Random Access Model.&lt;/p&gt;

&lt;p&gt;The rules are quite straightforward: any simple operation (+, -, =, /, *, if) takes one “step”. That’s how much it costs. It takes some constant time to execute each step. Yes, it sounds a little arbitrary but will be useful in the next paragraphs.&lt;/p&gt;

&lt;p&gt;Any loops and subroutines are made of simple operations. So if we have something like&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 step&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 step&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This costs 2 steps. And if we have&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;// aList is a List&amp;lt;int&amp;gt;() previously allocated&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sum&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="c1"&gt;// 1 step&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 step&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;n&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="c1"&gt;// n steps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;aList&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="c1"&gt;// 1 step&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This costs 

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;2+n∗1
 2 + n * 1
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;Not very exciting so far. Last example:&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;// n, m, and o are integers representing lengths of arrays&lt;/span&gt;
&lt;span class="c1"&gt;// a3dmatrix is, well, a 3d matrix&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&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="c1"&gt;// 1 step&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&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="c1"&gt;// 1 step&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;z&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="c1"&gt;// 1 step&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sum&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="c1"&gt;// 1 step&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;n&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="c1"&gt;// n steps&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;j&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;j&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt; &lt;span class="c1"&gt;// m steps&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;k&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;k&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt; &lt;span class="c1"&gt;// o steps&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a3dmatrix&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;j&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// 1 step&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;p&gt;In this case, the cost is &lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;4+n∗m∗o∗1
  4 + n * m * o * 1
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;4&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;o&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;Notice the algebraic nature of our analysis. We essentially have functions on the number of steps (i.e. the size of the input). The practical discussion on complexity then boils down to what’s the worst case scenario for an algorithmic implementation. In other words, given two (or more) options, we want to choose the one whose worst case is the lesser.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There also exists best case and average case scenarios, though they are usually not practically as important. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This type of analysis also has its own mathematical notation, known popularly as &lt;a href="https://en.m.wikipedia.org/wiki/Big_O_notation" rel="noopener noreferrer"&gt;Big-O notation&lt;/a&gt;, where O means “an order of magnitude”. So the previous complexity is of O(n * m * o). Notice we’ve left out the constants and kept only the variables. The intuition is that variable input sizes are the relevant components in complexity analysis, because what is made of only constant steps can all be considered to run in a constant time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sounds philosophical? It is! Mathematically speaking, the reasoning is of finding a function 
&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;f(x)f(x) &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;f&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
 which, given some constant 
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;CC &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
, is always smaller than or equal to another function 
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;g(x)∗Cg(x) * C &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;g&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∗&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
 for any 
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;x≥x0x ≥ x_0 &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≥&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;p&gt;Where this gets interesting is with recursive algorithms and intelligent data structures, in which the time taken can be manipulated by clever use of the stack and heap memories.&lt;/p&gt;

&lt;p&gt;Again, that’s not specific to C#. One might also argue that their code does not extensively use recursive functions or clever algorithms and data structures. Myth busted! 😝&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The .NET CLR uses these complex data structures and algorithms (it even calls some OS libraries, which also use them, not to mention disk I/O in some cases)&lt;/li&gt;
&lt;li&gt;3rd party libraries are definitely often solving complex issues with these strategies &lt;/li&gt;
&lt;li&gt;External resources, like drivers, databases and cloud services are all using the same theoretical premises&lt;/li&gt;
&lt;li&gt;The real value of a software solution is arguably how clever the underlying algorithm or heuristic is&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, effectively, are we&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Using databases? Ever wondered why that query on Entity Framework takes so long? The JOIN SQL clause is likely an O(
&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;n2n^2&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
) algorithm! But that index is a B-tree implementation with logarithmic complexity!&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using cloud services? Distributed algorithms will run everywhere, with loads of different heuristics, like leader election in replication or distance vector in between networking calls!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The GC has many implementations on heap collection strategy, like table based compaction, which runs in O(n * log(n)) time, where &lt;em&gt;n&lt;/em&gt; is the number of live objects.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These implementation details often get masked behind OOP, as that’s how it’s supposed to be. However, we should still not forget that procedural programming is where the solution eventually is. And that’s basically why asymptotic complexity analysis is so useful.&lt;/p&gt;

&lt;p&gt;Finally, notice there is a direct relationship between the memory consumed by an algorithm and the time it takes to compute. Wether we look at stack (like general recursiveness) or heap memory (like C# Arrays, Lists, Strings, Sets, Dictionaries, Collections, Concurrent data structures, or Objects in general), it should all optimize the solution complexity, minimizing its time cost. In other words, the memory cost should be justified by reductions in the time cost. A non-extensive list of examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple searches in collections could benefit from applying some ordering to them (we can implement the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.icomparable?view=net-7.0" rel="noopener noreferrer"&gt;IComparable&lt;/a&gt; interface and sort the collection)&lt;/li&gt;
&lt;li&gt;Loops should avoid allocating unnecessary objects, like
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;n&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;var&lt;/span&gt; &lt;span class="n"&gt;x&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;TextExtractor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// is it necessary to instantiate this object every iteration?&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;SomeMethod&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// is SomeMethod re-allocating memory?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Disposable objects should be disposed with &lt;code&gt;using&lt;/code&gt; statements or explicitly calling the &lt;code&gt;Dispose&lt;/code&gt; method. &lt;/li&gt;
&lt;li&gt;Exceptions should be treated as scenarios in which the program would crash, but should likely continue normal execution (not as part of regular processing)&lt;/li&gt;
&lt;li&gt;Both regular and unsafe memory (HttpClients, API packages - like AWS clients, DbConnections, FileStreams) should be carefully used considering their lifecycle (one object per request, per application, per scope…) &lt;/li&gt;
&lt;li&gt;Rendering frameworks (UI) also should be used as per their lifecycle (WPF, React, Vue, WinForms, and the other hundreds). One useful analysis is with remembering there will commonly be a virtual visual element tree, which will eventually be traversed with an mounting algorithm (usually a variation of DFS or BFS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of those examples are justified by the memory-time efficiency trade-off. In those cases and infinitely more, asymptotic computational complexity will explain what are the hot paths, the internal algorithm’s decision and how to better utilize it.&lt;/p&gt;

&lt;p&gt;If it seems like too many cases, don’t panic (towel!) Practice makes perfect and cases like the above will become nearly trivial with some time. The idea here is just that we must remember that algorithmic decisions have been made throughout our code base, and the memory we use is heavily affected by them.&lt;/p&gt;

&lt;p&gt;Thank you for reading so far! In the next (and I think, final) part we'll get hands-on on profiling memory in a dotnet app :)&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>profiling</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Memory management in C# - Part 1: 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>
