Introduction: The Cost of the Garbage Collector (GC)
One of the biggest advantages of modern .NET development is managed memory. Developers do not need to concern about manual memory allocation and deallocation like in C or C++. The .NET Garbage Collector (GC) get that concern on behalf of programmers, it automatically handles memory cleanup, making development safer and more productive. Here is the master class of C# memory management
However, “managed code” does not mean free performance.
When applications allocate too many objects on the heap, the Garbage Collector must frequently run to get back memory. This is the reason for several performance challenges:
- GC Pressure – Frequent object allocations increase the workload for the garbage collector.
- Stop-the-world pauses – The GC temporarily pauses application threads to get back memory.
- Heap fragmentation – Repeated allocations and deallocations create ineffective memory layouts.
In most enterprise applications, this overhead is acceptable. But in low-latency environments, even a small startup can cause a serious problem
What Are Span and Memory?
Span is one of the most advanced memory allocation method adding to .NET. It represents a lightweight view over contiguous memory.
Key characteristics:
- Can point to stack memory
- Can point to heap arrays
- Does not allocate memory
- Extremely fast
- Stack-only structure
Example:
Span<int> numbers = stackalloc int[5];
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
Here, we allocate an array directly on the stack. Benifit of this memory allocation does not involve garbage collector allocation. The user can experience faster execution and automatic memory release.
Because Span is stack-only, it cannot be used in async methods.
This is where Memory comes into the picture. Memory Provides a heap-safe wrapper around memory buffers.
Example:
Memory<byte> buffer = new byte[1024];
Span<byte> span = buffer.Span;
Zero-Allocation Patterns: Practical Examples
Now we are going to write a C# code for veryfy how these allocations are working. I create sample .NET console application for expain these zero allocation pattern
Over 15 year experiese, I can say most of the developers create extra allocations when working with strings. This is a way to reduce it.
The Scenario: Extracting a "Product Code" from a long String
In traditional C#, developers use .Substring() a method that creates a new string object on the heap every time it is called. If you are processing a 1GB file, this creates massive Garbage Collector (GC) pressure.
- The Project Setup
Create a Console Application for your benchmarks. Mixing benchmarks into your main Web API can skew the results due to background service interference.
Create a new console app
dotnet new console -n MyPerformanceLab
cd MyPerformanceLab
Add the BenchmarkDotNet nuget package (the industry standard)
- Designing Your Benchmark Class
Create a new file called StringPerformance.cs. Use the [MemoryDiagnoser] attribute; this is what adds the high-value "Allocated Memory" column to your results.
`using BenchmarkDotNet.Attributes;
namespace MyPerformanceLab
{
[MemoryDiagnoser]
[RankColumn]
public class StringPerformance
{
private const string TelemetryData = "ID:9982-XYZ-2026-LOG-DATA";
private const int Length = 8;
[Benchmark(Baseline = true)]
public string UsingSubstring()
{
int start = TelemetryData.IndexOf(':') + 1;
return TelemetryData.Substring(start, Length);
}
[Benchmark]
public ReadOnlySpan<char> UsingSpan()
{
ReadOnlySpan<char> span = TelemetryData.AsSpan();
int start = span.IndexOf(':') + 1;
return span.Slice(start, Length);
}
}
}`
- Triggering the Run
In your Program.cs, replace the default code with the BenchmarkRunner.
`using BenchmarkDotNet.Running;
// This triggers the heavy-duty measurement cycle
var summary = BenchmarkRunner.Run();`
This is my result. It unbelievable
Output: You can clearly see that using Span is 3x time faster than using Substring
You can also download the full source code for these benchmarks on my GitHub Repository.
This is my full article

Top comments (0)