
Code Optimization is a topic that gets a lot of attention. With the release of .NET 7, a thousand performance-impacting PRs went into the runtime and core libraries. This means that .NET 7 is fast, really fast, and it deserves a closer look if you want to optimize your codebase. In this article, we will look at a specific feature called Span<T>
and what it can do to improve the performance of your code.
Not quite what you were looking for? Try these links instead.
Code Optimization and Span<T>
Introduced in C#7.2 Span<T>
enables a window into contiguous regions of arbitrary memory. It also has a related type ReadOnlySpan<T>
that gives developers a read-only view of in-memory data. You can use this to peek into the memory space occupied by an immutable type, such as a string.
Span<T>
is limited to being allocated on the stack because it is defined as a ref struct. This is to ensure that there are no additional heap allocations. It is for this reason that you can improve the performance of your code. Let’s see an example of how to refactor a simple block of code to use ReadOnlySpan<T>
that will optimize the code and avoid unnecessary allocations.
An Example of using Span<T>
Consider the code below that uses substring to return a date.
class Program
{
private static readonly string _dateString = "12-03-2022";
static void Main(string[] args)
{
Console.WriteLine(SubstringMethod());
}
public static DateTime SubstringMethod()
{
var d = _dateString.Substring(3, 2);
var m = _dateString.Substring(0, 2);
var y = _dateString.Substring(6);
return new DateTime(int.Parse(y), int.Parse(m), int.Parse(d));
}
}
The method SubstringMethod
can be optimized using ReadOnlySpan
as illustrated in the following code block.
public static DateTime SpanMethod()
{
ReadOnlySpan<char> dateSpan = _dateString;
var d = dateSpan.Slice(3, 2);
var m = dateSpan.Slice(0, 2);
var y = dateSpan.Slice(6);
return new DateTime(int.Parse(y), int.Parse(m), int.Parse(d));
}
Here we use the Slice
method that takes an existing span and slices it into a smaller window. This is a low-cost operation because we aren’t copying anything. We are providing a new span that offers a window into a subset of the existing memory range.
Code Optimization Results
To see the benchmark results of the two methods, I added BenchmarkDotNet to the demo application. I created a class that contains the two methods I want to benchmark
[MemoryDiagnoser]
public class Bench
{
private static readonly string _dateString = "12-03-2022";
[Benchmark(Baseline = true)]
public DateTime SubstringMethod()
{
var d = _dateString.Substring(3, 2);
var m = _dateString.Substring(0, 2);
var y = _dateString.Substring(6);
return new DateTime(int.Parse(y), int.Parse(m), int.Parse(d));
}
[Benchmark]
public DateTime SpanMethod()
{
ReadOnlySpan<char> dateSpan = _dateString;
var d = dateSpan.Slice(3, 2);
var m = dateSpan.Slice(0, 2);
var y = dateSpan.Slice(6);
return new DateTime(int.Parse(y), int.Parse(m), int.Parse(d));
}
}
I then called this from my Main method in the following way.
static void Main(string[] args)
{
BenchmarkRunner.Run<Bench>();
}
Running the Console Application in release mode produced the results in the screenshot below.

Apart from the reduction from 63.52 ns to 35.72 ns, the SpanMethod
did not make any allocations on the heap. To visualize this, let’s look at the difference using two simple diagrams to represent using Substring and Span.
Visualizing What this Means
Strings are immutable. This means that once they are created, they can’t be changed. If you create a substring from a string, you create a new memory allocation on the heap. For the SubstringMethod
we can think of the allocations as follows.
When the date string is created, it will have a memory address represented by 0x01
. In reality, the memory address is much longer, but for our purposes, bear with me. The address is stored along with a pointer to the memory space to refer to the string and use it on the stack.
Because strings are immutable, a new memory allocation is created for each substring you make for the value containing the substring value. You are, in effect allocating double the memory on the heap. Once this goes out of scope and no more references point to them, garbage collection will occur.

If, however, we use Span
the picture looks a lot different, as illustrated below. Thinking of the code that is used to get the month value of 12, we used dateSpan.Slice(0, 2)
. The span will store the memory address of the string and also keeps an offset of 0, resulting in 0x01+0. The following address also holds the length of 2
that it must read. This is a pointer to a specific subset of data at the memory address containing the string. The best is that we didn’t have to allocate any additional memory.

Conclusion
The reason using span is so memory efficient is because it only deals with already allocated memory. Be sure to look at more resources online for information regarding Span<T> and see how it will benefit you.