What Is Span?
Span is a ref struct
in c# that can help you save a lot of memory allocation. Because span is ref struct
it can be only allocated on the stack and not on the heap which means it does not require garbage collection which effectively means that there gonna be no pause in your application for garbage collection. Let's see what span is and how you can use it with an example:
Example
In this example, we need to get extract the day, month, year from a string that is formatted in a specific way. For that, we can use the Substring
method.
With Substring
var dateAsText = "03 02 2022";
// Create A Tuple With (Day, Month, Year) In It.
(int day, int month, int year) DateAsTupleWithSubstring()
{
var dayAsText = dateAsText.Substring(0, 2);
var monthAsText = dateAsText.Substring(3, 2);
var yearAsText = dateAsText.Substring(6);
var day = int.Parse(dayAsText);
var month = int.Parse(monthAsText);
var year = int.Parse(yearAsText);
return (day, month, year);
}
It looks just right but here is a problem that this is allocating a string on the heap each time we use the Substring
method which is expensive because then the garbage collector needs to go and do some garbage collection which effectively makes your application pause. We can fix this issue using a ReadOnlySpan
.
With Span
var dateAsText = "03 02 2022";
// Create a tuple with (Day, Month, Year) in it.
(int day, int month, int year) DateAsTupleWithSpan()
{
ReadOnlySpan<char> dateAsSpan = dateAsText;
var dayAsText = dateAsSpan.Slice(0, 2);
var monthAsText = dateAsSpan.Slice(3, 2);
var yearAsText = dateAsSpan.Slice(6);
var day = int.Parse(dayAsText);
var month = int.Parse(monthAsText);
var year = int.Parse(yearAsText);
return (day, month, year);
}
This code actually performs better than the previous one we will talk about how it does that after benchmarking both functions let's do some benchmarking now with the Benchmark.NET
library. If you need to know how to use the Benchmark.NET library to Benchmark.NET code read this blog post: Benchmark Your .NET Code With Benchmark.NET
Benchmarks
Defining Benchmarks
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
public class SpanVsSubstring
{
private readonly string dateAsText = "03 02 2022";
[Benchmark]
// Create A Tuple With (Day, Month, Year) In It.
public (int day, int month, int year) DateAsTupleWithSubstring()
{
var dayAsText = dateAsText.Substring(0, 2);
var monthAsText = dateAsText.Substring(3, 2);
var yearAsText = dateAsText.Substring(6);
var day = int.Parse(dayAsText);
var month = int.Parse(monthAsText);
var year = int.Parse(yearAsText);
return (day, month, year);
}
[Benchmark]
public (int day, int month, int year) DateAsTupleWithSpan()
{
ReadOnlySpan<char> dateAsSpan = dateAsText;
var dayAsText = dateAsSpan.Slice(0, 2);
var monthAsText = dateAsSpan.Slice(3, 2);
var yearAsText = dateAsSpan.Slice(6);
var day = int.Parse(dayAsText);
var month = int.Parse(monthAsText);
var year = int.Parse(yearAsText);
return (day, month, year);
}
}
Benchmark Results
Method | Mean | Error | StdDev | Median | Gen 0 | Allocated |
DateAsTupleWithSubstring | 143.13 ns | 8.278 ns | 24.147 ns | 134.34 ns | 0.0918 | 96 B |
DateAsTupleWithSpan | 69.98 ns | 2.363 ns | 6.780 ns | 68.32 ns | - | - |
As you can see the DateAsTupleWithSpan
function is more than half faster than the DateAsTupleWithSubstring
one which is a big performance improvement but the interesting thing that happens on the memory allocation which you can see the span did not allocate even a single byte on the heap where the substring one allocated 96B
on the heap which then needs to be garbage collected through the garbage collector which again pauses your application. But really how span did the same thing that substring did but without allocating even a single byte on the heap. Here's a diagram that shows this.