DEV Community

mohamed Tayel
mohamed Tayel

Posted on

4

Introducing Span<T> in .NET

Meta Description:

Discover the power of Span<T> in .NET for efficient memory management. Learn how to slice arrays without copying, improve performance, and ensure immutability with ReadOnlySpan<T>. Includes detailed examples, comparisons, and best practices for modern .NET development.

When working with large arrays or datasets, you may need to process a specific portion of the data. Traditionally, slicing an array creates new arrays, which increases memory usage and can hurt performance. To solve this, .NET introduces Span<T>, a memory-efficient type that allows you to work with contiguous regions of memory without unnecessary allocations.


What is Span<T>?

Span<T> is a stack-only type that provides a safe and efficient way to represent a contiguous block of memory. It allows you to:

  • Work with slices of an array or buffer without creating new arrays.
  • Reference memory without copying data.
  • Reduce allocations and improve performance.

Key Features

  • No Copying: Slicing a Span<T> doesn’t create new arrays; it points to the same memory.
  • Implicit Conversion: Arrays (T[]) are automatically converted to Span<T> for ease of use.
  • Stack-Based: Optimized for short-lived, high-performance operations.

Limitations

  • Cannot be used in asynchronous methods.
  • Cannot be stored as a class field.
  • Only valid within the current stack frame.

Why Use Span<T>?

  1. Memory Efficiency: Avoid creating new arrays while working with slices of data.
  2. Performance: Reduces heap allocations and garbage collection pressure.
  3. Ease of Use: Simplifies operations with array slicing and manipulation.

Using Span<T>: A Practical Example

Suppose you have a large array of integers representing sales data. You want to process the last 100 values to perform validation.

Without Span<T>

using System;

class Program
{
    static void Main()
    {
        int[] salesData = new int[1000];
        Random random = new Random();
        for (int i = 0; i < salesData.Length; i++)
        {
            salesData[i] = random.Next(1, 100);
        }

        // Slicing the array (creates a new array)
        int[] last100Sales = new int[100];
        Array.Copy(salesData, salesData.Length - 100, last100Sales, 0, 100);

        // Validate the sliced array
        bool isValid = ValidateWithoutSpan(last100Sales);
        Console.WriteLine($"Validation result: {isValid}");
    }

    static bool ValidateWithoutSpan(int[] dataSlice)
    {
        foreach (int value in dataSlice)
        {
            if (value > 90)
            {
                return false; // Validation failed
            }
        }
        return true; // Validation passed
    }
}
Enter fullscreen mode Exit fullscreen mode

Drawbacks

  • Extra Allocations: Array.Copy creates a new array.
  • Performance Overhead: More memory usage and increased garbage collection pressure.

With Span<T>

using System;

class Program
{
    static void Main()
    {
        int[] salesData = new int[1000];
        Random random = new Random();
        for (int i = 0; i < salesData.Length; i++)
        {
            salesData[i] = random.Next(1, 100);
        }

        // Create a span directly from the array
        Span<int> salesSpan = salesData;

        // Slice the last 100 values
        Span<int> last100Sales = salesSpan[^100..];

        // Validate the sliced span
        bool isValid = ValidateWithSpan(last100Sales);
        Console.WriteLine($"Validation result: {isValid}");
    }

    static bool ValidateWithSpan(Span<int> dataSlice)
    {
        foreach (int value in dataSlice)
        {
            if (value > 90)
            {
                return false; // Validation failed
            }
        }
        return true; // Validation passed
    }
}
Enter fullscreen mode Exit fullscreen mode

Advantages

  • No Extra Allocations: The slice directly references the original array.
  • Better Performance: Reduced memory usage and faster processing.

Ensuring Data Integrity with ReadOnlySpan<T>

If you want to ensure that the original data remains unchanged, use ReadOnlySpan<T>. It guarantees immutability by preventing modifications to the data.

using System;

class Program
{
    static void Main()
    {
        int[] salesData = new int[1000];
        Random random = new Random();
        for (int i = 0; i < salesData.Length; i++)
        {
            salesData[i] = random.Next(1, 100);
        }

        // Create a read-only span
        ReadOnlySpan<int> salesSpan = salesData;

        // Slice the last 100 values
        ReadOnlySpan<int> last100Sales = salesSpan[^100..];

        // Validate the read-only span
        bool isValid = ValidateReadOnlySpan(last100Sales);
        Console.WriteLine($"Validation result: {isValid}");
    }

    static bool ValidateReadOnlySpan(ReadOnlySpan<int> dataSlice)
    {
        foreach (int value in dataSlice)
        {
            if (value > 90)
            {
                return false; // Validation failed
            }
        }
        return true; // Validation passed
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of ReadOnlySpan<T>

  • Prevents accidental modifications to the data.
  • Ensures immutability for safer operations.

Comparing Performance: Span<T> vs. Without Span<T>

Here’s a performance comparison for processing slices:

using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        int[] data = new int[10_000_000];
        Random random = new Random();
        for (int i = 0; i < data.Length; i++)
        {
            data[i] = random.Next(1, 100);
        }

        Stopwatch stopwatch = new Stopwatch();

        // Without Span<T>
        stopwatch.Start();
        ProcessWithoutSpan(data);
        stopwatch.Stop();
        Console.WriteLine($"Without Span<T>: {stopwatch.ElapsedMilliseconds} ms");

        // With Span<T>
        stopwatch.Restart();
        ProcessWithSpan(data);
        stopwatch.Stop();
        Console.WriteLine($"With Span<T>: {stopwatch.ElapsedMilliseconds} ms");
    }

    static void ProcessWithoutSpan(int[] data)
    {
        for (int i = 0; i < 100; i++)
        {
            int[] slice = new int[100];
            Array.Copy(data, i * 100, slice, 0, 100);
            ValidateWithoutSpan(slice);
        }
    }

    static void ProcessWithSpan(int[] data)
    {
        Span<int> span = data;
        for (int i = 0; i < 100; i++)
        {
            Span<int> slice = span.Slice(i * 100, 100);
            ValidateWithSpan(slice);
        }
    }

    static bool ValidateWithoutSpan(int[] dataSlice)
    {
        foreach (int value in dataSlice)
        {
            if (value > 90) return false;
        }
        return true;
    }

    static bool ValidateWithSpan(Span<int> dataSlice)
    {
        foreach (int value in dataSlice)
        {
            if (value > 90) return false;
        }
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices for Using Span<T>

  1. Use for Stack-Based Operations: Span<T> is ideal for short-lived operations within a stack frame.
  2. Prefer ReadOnlySpan<T> for Immutability: Use ReadOnlySpan<T> when data should not be modified.
  3. Avoid in Async Methods: Use Memory<T> for asynchronous workflows.
  4. Minimize Conversions: Convert collections (e.g., List<T>) to arrays once, then use spans for further processing.
  5. Leverage Range Syntax: Use slicing ([^start..end]) for simplicity and efficiency.

Conclusion

Span<T> provides a high-performance, memory-efficient way to handle slices of arrays or other memory regions. By avoiding unnecessary allocations and leveraging stack-based operations, you can significantly improve the performance of your .NET applications. Use ReadOnlySpan<T> for immutability and Memory<T> for asynchronous scenarios to maximize the utility of this powerful tool.

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay