Hello there
My current technology stack: .NET 9, Python, TypeScript, and Azure.
I develop microservices and terraform of different sizes. Sharing my challenges and key learning.
About
The views expressed in this blog are my own and do not reflect my employer's. I am not responsible for any consequences of using the information provided. This blog is for educational purposes only, not for commercial use. Readers should apply their own judgment.
Span vs Memory vs ArraySegment vs ReadOnlySequence - Performance in .NET 9
.NET 9 Memory Optimization: Span vs Memory vs ArraySegment vs ReadOnlySequence
.NET 9 introduces further optimizations for memory-efficient programming, making it essential to understand Span
Why Compare These Memory Types?
Efficient memory usage is crucial in performance-sensitive applications such as:
- High-speed networking (e.g., processing large byte buffers)
- File handling (e.g., reading/writing large datasets efficiently)
- Streaming data processing (e.g., parsing logs, video/audio processing)
- Zero-allocation performance (e.g., minimizing GC pressure in high-throughput systems)
Each of these types has specific advantages:
Span<T>– Fastest for stack-based, contiguous memory manipulation.Memory<T>– Works across heap and stack, supporting async scenarios.ArraySegment<T>– A lightweight wrapper for slicing arrays efficiently.ReadOnlySequence<T>– Optimized for pipeline-based streaming data.
Key Performance Considerations
| Feature | Span |
Memory |
ArraySegment |
ReadOnlySequence |
|---|---|---|---|---|
| Heap Allocation | ❌ No | ✅ Yes (if heap-based) | ✅ Yes | ✅ Yes |
| Stack Allocation | ✅ Yes | ❌ No | ❌ No | ❌ No |
| Supports Async | ❌ No | ✅ Yes | ❌ No | ✅ Yes |
| Mutation Allowed | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No (Read-Only) |
| Ideal for Slicing | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Best for Pipelines | ❌ No | ❌ No | ❌ No | ✅ Yes |
Benchmark Setup
We will compare Span
- String Manipulation & Slicing
- Large Dataset Iteration
- Buffer Processing Performance
Benchmark Code
public class MemoryOptimizationBenchmark
{
private readonly byte[] _buffer = Enumerable.Repeat((byte)42, 1000000).ToArray();
public void SpanSlice()
{
Span<byte> span = _buffer.AsSpan(500000, 500000);
byte sum = 0;
foreach (var item in span)
sum += item;
}
public void MemorySlice()
{
Memory<byte> memory = _buffer.AsMemory(500000, 500000);
byte sum = 0;
foreach (var item in memory.Span)
sum += item;
}
public void ArraySegmentSlice()
{
ArraySegment<byte> segment = new ArraySegment<byte>(_buffer, 500000, 500000);
byte sum = 0;
foreach (var item in segment)
sum += item;
}
public void ReadOnlySequenceSlice()
{
ReadOnlySequence<byte> sequence = new ReadOnlySequence<byte>(_buffer);
byte sum = 0;
foreach (var memory in sequence)
{
foreach (var item in memory.Span)
sum += item;
}
}
}
Test Environment
- Processor: 13th Gen Intel Core i7-1370P, 1 CPU, 20 logical and 14 physical cores
- .NET SDK: 9.0.103
- Runtime: .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
Benchmark Results
| Method | Mean | Error | StdDev | Median | Allocated |
|---|---|---|---|---|---|
| SpanSlice | 205.0 us | 1.53 us | 3.60 us | 204.1 us | - |
| MemorySlice | 167.5 us | 1.60 us | 1.50 us | 167.2 us | - |
| ArraySegmentSlice | 267.5 us | 29.21 us | 86.12 us | 215.9 us | - |
| ReadOnlySequenceSlice | 296.3 us | 4.57 us | 4.05 us | 295.4 us | - |
Analysis & Key Takeaways
Memory<T>outperformsSpan<T>in this benchmark, likely due to optimizations in .NET 9.Span<T>remains a great choice for low-level, stack-allocated scenarios, but performance depends on the use case.ArraySegment<T>has high variance, making it less predictable in large-scale applications.ReadOnlySequence<T>is the slowest, but is designed for handling fragmented data streams, making it useful for streaming APIs and pipelines.
Best Practices
- ✔️ Use
Span<T>when performance is critical and data can fit in stack memory. - ✔️ Use
Memory<T>for async-friendly memory handling. - ✔️ Use
ArraySegment<T>when working with existing arrays. - ✔️ Use
ReadOnlySequence<T>when handling large, pipeline-based data streams. - ✔️ Avoid unnecessary heap allocations by using Span
wherever possible .
Conclusion
For memory-efficient applications in .NET 9:
Memory<T>performed best in this benchmark, showing its efficiency in heap-allocated memory.Span<T>remains excellent for stack-allocated data, but may not always be the fastest.ArraySegment<T>can be inconsistent due to runtime optimizations.ReadOnlySequence<T>is slower but optimized for pipelines and streaming data.