{"id":54280,"date":"2025-12-01T05:22:04","date_gmt":"2025-12-01T05:22:04","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=54280"},"modified":"2025-12-01T05:22:04","modified_gmt":"2025-12-01T05:22:04","slug":"dotnet-memory-optimization-in-net-with-span","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/dotnet-memory-optimization-in-net-with-span\/","title":{"rendered":"DOTNET: Memory Optimization in .NET with Span"},"content":{"rendered":"\n<p>This is a perfect demo topic. Here\u2019s a <strong>single, self-contained console app<\/strong> that lets you <em>feel<\/em> the difference between:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udeab <strong>Without <code>Span&lt;T><\/code><\/strong> \u2013 using <code>Substring<\/code> (allocates new strings every time)<\/li>\n\n\n\n<li>\u2705 <strong>With <code>Span&lt;T><\/code><\/strong> \u2013 using <code>AsSpan<\/code> + <code>Slice<\/code> (no extra allocations)<\/li>\n<\/ul>\n\n\n\n<p>You\u2019ll get timing + GC stats for both in the same run.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1\ufe0f\u20e3 Full Code \u2013 <code>Program.cs<\/code><\/h2>\n\n\n\n<p>Copy\u2013paste this as <strong>Program.cs<\/strong> (or replace the existing one in a new console app):<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/devops-school\/deb3eba93300e352e9a464a84f7de072.js\"><\/script>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2\ufe0f\u20e3 (Optional) Minimal <code>.csproj<\/code><\/h2>\n\n\n\n<p>If you want a fully explicit project file, create <strong>SpanDemo.csproj<\/strong>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Project<\/span> <span class=\"hljs-attr\">Sdk<\/span>=<span class=\"hljs-string\">\"Microsoft.NET.Sdk\"<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PropertyGroup<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">OutputType<\/span>&gt;<\/span>Exe<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">OutputType<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">TargetFramework<\/span>&gt;<\/span>net8.0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">TargetFramework<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ImplicitUsings<\/span>&gt;<\/span>enable<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ImplicitUsings<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Nullable<\/span>&gt;<\/span>enable<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Nullable<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">PropertyGroup<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Project<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can change <code>net8.0<\/code> to <code>net9.0<\/code> or <code>net10.0<\/code> depending on your installed SDK.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3\ufe0f\u20e3 Step-by-Step: How to Run It<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1 \u2013 Create a new console app<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">dotnet <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">console<\/span> -n SpanDemo\ncd SpanDemo\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Step 2 \u2013 Replace <code>Program.cs<\/code><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Open <code>Program.cs<\/code><\/li>\n\n\n\n<li>Delete everything<\/li>\n\n\n\n<li>Paste the full code from section <strong>1\ufe0f\u20e3<\/strong><\/li>\n<\/ul>\n\n\n\n<p>(Optionally replace the generated <code>.csproj<\/code> with the one in <strong>2\ufe0f\u20e3<\/strong>.)<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3 \u2013 Build and run in <strong>Release<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">dotnet run -c Release\n<\/code><\/span><\/pre>\n\n\n<p>You\u2019ll see output similar to:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">=========================================\n      Span<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">T<\/span>&gt;<\/span> Demo \u2013 Substring vs Span    \n=========================================\n\nItems to process: 500,000\n\nPreparing test data...\nWarming up (small runs)...\n--- Warmup \u2013 Substring (no Span) ---\nItems processed : 50,000\nTime Elapsed    : 45 ms\nGC Gen0         : 5\nGC Gen1         : 0\nGC Gen2         : 0\nManaged Memory \u0394: 2.30 MB\nChecksum (ignore, just prevents JIT from optimizing away work): 1234567\n\n--- Warmup \u2013 Span<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">T<\/span>&gt;<\/span> ---\nItems processed : 50,000\nTime Elapsed    : 20 ms\nGC Gen0         : 1\nGC Gen1         : 0\nGC Gen2         : 0\nManaged Memory \u0394: 0.20 MB\nChecksum (ignore, just prevents JIT from optimizing away work): 1234567\n\n=========== REAL TESTS (Release) ==========\n\n--- WITHOUT Span<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">T<\/span>&gt;<\/span> \u2013 using Substring (allocations) ---\nItems processed : 500,000\nTime Elapsed    : 400 ms\nGC Gen0         : 40\nGC Gen1         : 2\nGC Gen2         : 0\nManaged Memory \u0394: 20.50 MB\nChecksum (ignore, just prevents JIT from optimizing away work): 1234567\n\n--- WITH Span<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">T<\/span>&gt;<\/span> \u2013 using AsSpan + Slice (no extra allocations) ---\nItems processed : 500,000\nTime Elapsed    : 180 ms\nGC Gen0         : 4\nGC Gen1         : 0\nGC Gen2         : 0\nManaged Memory \u0394: 1.10 MB\nChecksum (ignore, just prevents JIT from optimizing away work): 1234567\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>(Your exact numbers will vary by machine, but the <strong>pattern<\/strong> should be similar.)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4\ufe0f\u20e3 How to \u201cExperience\u201d and Interpret the Results<\/h2>\n\n\n\n<p>Look at these metrics for each scenario:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u23f1 <strong>Time Elapsed<\/strong><\/li>\n\n\n\n<li>\ud83d\uddd1 <strong>GC Gen0 \/ Gen1 \/ Gen2<\/strong><\/li>\n\n\n\n<li>\ud83d\udcbe <strong>Managed Memory \u0394 (MB)<\/strong><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udd34 Scenario 1 \u2013 WITHOUT <code>Span&lt;T&gt;<\/code> (using <code>Substring<\/code>)<\/h3>\n\n\n\n<p>What happens:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>For each of the 500,000 strings, we call <code>Substring<\/code><strong>three times<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Each <code>Substring<\/code> creates a <strong>new string allocation on the heap<\/strong>.<\/li>\n\n\n\n<li>So we allocate 1.5M strings in the loop.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>You\u2019ll typically see:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Higher elapsed time<\/strong><\/li>\n\n\n\n<li><strong>Many more Gen0 collections<\/strong><\/li>\n\n\n\n<li>Possibly some Gen1\/Gen2 collections<\/li>\n\n\n\n<li>Larger <strong>Managed Memory \u0394<\/strong><\/li>\n<\/ul>\n\n\n\n<p>This simulates a typical <strong>string parsing \/ text processing<\/strong> pattern that is allocation-heavy.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udfe2 Scenario 2 \u2013 WITH <code>Span&lt;T&gt;<\/code><\/h3>\n\n\n\n<p>What happens:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We still have the same 500,000 original strings (same cost as before).<\/li>\n\n\n\n<li>But inside the loop we use: <code>ReadOnlySpan&lt;char> span = s.AsSpan(); var part1 = span.Slice(0, 8); var part2 = span.Slice(9, 3); var part3 = span.Slice(span.Length - 10);<\/code><\/li>\n\n\n\n<li>These are <strong>just views<\/strong> (windows) over the same underlying string:\n<ul class=\"wp-block-list\">\n<li>No new string objects are created.<\/li>\n\n\n\n<li>No extra heap allocations per slice.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>You should see:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Lower elapsed time<\/strong> (less allocation + less GC work)<\/li>\n\n\n\n<li><strong>Much fewer Gen0 collections<\/strong><\/li>\n\n\n\n<li>Gen1\/Gen2 often drop to <strong>zero<\/strong><\/li>\n\n\n\n<li><strong>Managed Memory \u0394<\/strong> is much smaller<\/li>\n<\/ul>\n\n\n\n<p>This is exactly the benefit of <code>Span&lt;T&gt;<\/code>:<br>\ud83d\udd39 <strong>Operate on slices of data without additional allocations.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5\ufe0f\u20e3 Playing with the Load<\/h2>\n\n\n\n<p>To make the impact more dramatic (for demo):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Increase item count: <code>const int itemCount = 1_000_000;<\/code><\/li>\n\n\n\n<li>Or add more slicing and comparisons per string.<\/li>\n<\/ul>\n\n\n\n<p>Just be aware that very large values can make the <em>Substring<\/em> scenario quite heavy.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6\ufe0f\u20e3 How to Explain This in Training<\/h2>\n\n\n\n<p>You can summarize it like this:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<ul class=\"wp-block-list\">\n<li><strong>Without <code>Span&lt;T><\/code><\/strong>, every <code>Substring<\/code> call allocates a new string. In a tight loop, this leads to <strong>tons of small GC\u2019ed objects<\/strong>, more GC cycles, and higher latency.<\/li>\n\n\n\n<li><strong>With <code>Span&lt;T><\/code><\/strong>, we use <code>AsSpan<\/code> + <code>Slice<\/code> to work with <strong>slices of the existing string<\/strong>. No new allocations \u2192 <strong>lower GC pressure<\/strong> \u2192 <strong>better throughput<\/strong>.<\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a perfect demo topic. Here\u2019s a single, self-contained console app that lets you feel the difference between: You\u2019ll get timing + GC stats for both in the same&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-54280","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54280","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=54280"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54280\/revisions"}],"predecessor-version":[{"id":54281,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54280\/revisions\/54281"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=54280"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=54280"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=54280"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}