Table of Contents

ExSpans

ExSpans: Extended spans of nint index range (nint 索引范围的扩展跨度).

Package Nuget Description
ExSpans ExSpans Extended spans of nint index range (nint 索引范围的扩展跨度). Commonly types: ExMemoryExtensions, ExNativeMemory.
ExSpans.Core ExSpans Extended spans of nint index range - Core type (nint 索引范围的扩展跨度 - 核心类型). Commonly types: ExSpan<T>, ReadOnlyExSpan<T>, ExMemoryMarshal, SafeBufferSpanProvider.

Purpose (用途)

Span, introduced in C# 7.2, is a new structure that allows developers to access contiguous regions of arbitrary memory in a type-safe manner. It works with both managed memory (e.g., arrays) and unmanaged memory (e.g., memory allocated via Marshal.AllocHGlobal), and does not require memory copying, thus improving performance (Span 是 C# 7.2 引入的一种新结构, 允许开发者以类型安全的方式访问任意内存的连续区域. 它既可以用于托管内存(如数组), 又可以用于非托管内存(如通过 Marshal.AllocHGlobal 分配的内存), 并且不需要进行内存复制, 从而提高性能).

However, Span has a limitation: it uses int (Int32: 32-bit integer) index. Even on 64-bit operating systems, it can only access data up to 2G(2^31) in length. The Marshal.AllocHGlobal method supports nint (IntPtr: native-sized integer) lengths for memory allocation, allowing allocations exceeding 2GB on 64-bit systems—something Span struggles with. Manually manipulating unmanaged memory without Span is cumbersome and the code is not very generalizable (然而 Span 存在一个局限性, 它使用的是 int (Int32: 32位整数) 类型的索引. 即使是在 64位操作系统中, 它仅能访问最长 2G(2^31) 的数据. 而 Marshal.AllocHGlobal 方法在分配内存时支持 nint (IntPtr: 原生整数) 类型的长度, 在 64位系统上能分配超过 2GB 的非托管内存, Span 难以支持这么长的数据. 在没有 Span 的时候, 手动操作非托管内存是非常繁琐的, 而且代码的通用性不高).

ExSpan solves this limitation by using nint type indexes. The byte size of nint matches that of a native pointer, enabling 64-bit index on 64-bit systems. ExSpan is used in exactly the same way as Span, and provides a large number of utility functions like Span. This makes it suitable for image processing, video processing, deep learning, and other large-scale data areas (ExSpan 解决了这一局限性, 它使用 nint 类型的索引. nint 类型的字节大小, 与原生指针完全相同, 故在64位系统上能以64位的索引来访问数据. ExSpan 的用法与 Span 完全相同, 且像 Span 那样提供了大量的工具函数. 这使得它适用于 图像处理、视频处理、深度学习等大规模数据的领域).

ExSpan inherits the advantages of Span (ExSpan 继承了Span 的优点):

  • Zero-allocation. ExSpan is a stack-allocated struct, avoiding heap allocations and reducing garbage collection overhead (零分配. ExSpan 是一个零分配的表示形式, 意味着它不会在堆上分配内存, 而是分配在栈上, 这样可以减少垃圾回收的负担).
  • Safety. ExSpan provides safe memory access, avoiding the risks associated with pointer manipulation, such as buffer overflows and null pointer access (安全性. ExSpan 提供了安全的内存访问, 避免了指针操作带来的风险, 如缓冲区溢出和空指针访问).
  • Generality. Can be used in managed memory (e.g. arrays) as well as unmanaged memory (e.g., memory allocated via Marshal.AllocHGlobal) (通用性. 既可以用于托管内存(如数组), 又可以用于非托管内存(如通过 Marshal.AllocHGlobal 分配的内存)).
  • Slicing capabilities. ExSpan supports slicing operations, making it easy to create an ExSpan that points to a part of an array or block of memory without having to copy the data (切片功能. ExSpan 支持切片操作, 可以轻松创建指向数组或内存块某一部分的 ExSpan, 而无需复制数据).
  • High performance. Due to the design of ExSpan, the performance of manipulating it is close to that of directly manipulating an array. This makes it suitable for high-performance applications, such as buffer data processing, string parsing, image processing, and so on (高性能. 由于 ExSpan 的设计, 操作它的性能接近于直接操作数组. 使其适合高性能应用场景, 如 缓冲区数据处理、字符串解析、图像处理 等).
  • Feature-rich. Like Span, it provides a large number of utility functions. For example, ExMemoryMarshal instead of MemoryMarshal, ExMemoryExtensions instead of MemoryExtensions, and ExNativeMemory instead of NativeMemory. They also provide classes such as SafeBufferSpanProvider. They utilize the VectorTraits library to implement cross-platform SIMD hardware acceleration (功能丰富. 像 Span 那样提供了大量的工具函数. 例如用 ExMemoryMarshal 替代 MemoryMarshal, 用 ExMemoryExtensions 替代 MemoryExtensions, 用 ExNativeMemory 替代 NativeMemory. 还提供了 SafeBufferSpanProvider 等类. 且它们利用 VectorTraits 库, 实现了跨平台的SIMD硬件加速).

This library also has these advantages (本库还具有这些优点). 

  • Multiple .NET versions are supported. Since NET Framework 4.5, to the latest .NET 9, all supported. Also supported NET Standard 1.1 ~ .NET Standard 2.1 (支持多种 .NET版本. 从 .NET Framework 4.5, 到最新的 .NET 9, 全都支持. 而且支持 .NET Standard 1.1 ~ .NET Standard 2.1).
  • Porting new versions of features. It provides the latest Span functionality to earlier versions of .NET, such as .NET 6.0's added MemoryExtensions.TryWrite methods (移植新版本的功能. 能给早期版本的.NET, 提供最新的 Span功能. 例如 .NET 6.0 新增的 MemoryExtensions.TryWrite 方法).
  • Cross-platform. It is composed entirely of managed code and supports Windows, Linux, MacOS, iOS, Android, Wasm, and more. It avoids the tedious task of "choosing a different native dll for the current platform" (跨平台. 它完全由托管代码所组成, 能够支持 Windows, Linux, MacOS, iOS, Android, Wasm 等平台. 能避免繁琐的“根据当前平台选择不同的原生dll”工作).
  • Native AOT supported. Native AOT technology can be used to compile the program into native code (machine code) for the target platform when needed. NET runtime is no longer needed and has the advantage of faster startup (支持原生AOT. 当需要时, 可以利用原生AOT技术, 将程序编译为目标平台的原生代码(机器码). 此时不再需要 .NET 运行时, 且具有启动速度快等优点).

Getting started (入门指南)

1) Install via NuGet (通过NuGet安装)

This library can be installed using the "Package Manager" GUI. Alternatively, you can install it by entering the following command in the "Package Manager Console" (可以使用“包管理器”GUI来安装本库. 或可在“包管理器控制台”里输入以下命令进行安装).

NuGet: PM> Install-Package ExSpans

2) Simple Example (简单范例)

A checksum calculation function (一个计算校验和的函数)

First, we use ReadOnlySpan to implement a function that calculates the checksum (首先, 我们用 ReadOnlySpan 实现一个计算校验和的函数).

static int SumSpan(ReadOnlySpan<int> span) {
    int rt = 0; // Result.
    for (int i = 0; i < span.Length; i++) {
        rt += span[i];
    }
    return rt;
}

The function can then be modified with the ReadOnlyExSpan type from the ExSpans library. Simply change ReadOnlySpan to ReadOnlyExSpan and change the index type from int to nint, and you're done (随后可以用 ExSpans 库中的 ReadOnlyExSpan 类型来改造这个函数. 仅需将 ReadOnlySpan 改为 ReadOnlyExSpan, 再将索引类型从 int 改为 nint, 便完成了改造).

static int SumExSpan(ReadOnlyExSpan<int> span) {
    int rt = 0; // Result.
    for (nint i = 0; i < span.Length; i++) {
        rt += span[i];
    }
    return rt;
}

Full code of the program (完整程序的代码)

The usage of ExSpan (or ReadOnlyExSpan) is exactly the same as Span (or ReadOnlySpan), except that the index type has been changed from int to nint (ExSpan(或 ReadOnlyExSpan) 的用法, 与 Span(或 ReadOnlySpan) 完全相同, 仅是索引类型从 int 改为了 nint).  This library provides a large number of utility functions like Span. For example, ExMemoryMarshal is used instead of MemoryMarshal, ExMemoryExtensions is used instead of MemoryExtensions, and ExNativeMemory is used instead of NativeMemory. The Count method used in the following example is an extension method of ExMemoryExtensions (本库像 Span 那样提供了大量的工具函数. 例如用 ExMemoryMarshal 替代 MemoryMarshal, 用 ExMemoryExtensions 替代 MemoryExtensions, 用 ExNativeMemory 替代 NativeMemory. 后面范例代码中使用的 Count 方法, 是 ExMemoryExtensions 里的扩展方法).

Using type conversion operators or extension methods such as AsSpan/AsExSpan, it is easy to perform type conversion between ExSpan (or ReadOnlyExSpan) and Span (or ReadOnlySpan) (使用类型转换运算符, 或是 AsSpan/AsExSpan 等扩展方法, 可以方便的将 ExSpan(或 ReadOnlyExSpan) 与 Span(或 ReadOnlySpan) 进行类型转换).

Below are various usages displayed (下面展示了各种用法).

using System;
using System.IO;
using Zyl.ExSpans;

namespace Zyl.ExSpans.Sample {

    internal class Program {
        static void Main(string[] args) {
            TextWriter writer = Console.Out;
            OutputHeader(writer);

            // Test some.
            TestSimple(writer);
            Test2GB(writer);
        }

        internal static void OutputHeader(TextWriter writer) {
            writer.WriteLine("ExSpans.Sample");
            writer.WriteLine();
        }

        static void TestSimple(TextWriter writer) {
            const int bufferSize = 16;
            // Create ExSpan by Array.
            int[] sourceArray = new int[bufferSize];
            TestExSpan(writer, "Array", new ExSpan<int>(sourceArray)); // Use constructor method.
            //TestExSpan(writer, "Array", sourceArray.AsExSpan()); // Or use extension method.
            writer.WriteLine();

            // Create ExSpan by Span.
            Span<int> sourceSpan = stackalloc int[bufferSize];
            TestExSpan(writer, "Span", sourceSpan); // Use implicit conversion.
            //TestExSpan(writer, "Span", sourceSpan.AsExSpan()); // Or use extension method.

            // Convert ExSpan to Span.
            ExSpan<int> intSpan = sourceSpan; // Implicit conversion Span to ExSpan.
            Span<int> span = (Span<int>)intSpan; // Use explicit conversion.
            //Span<int> span = intSpan.AsSpan(); // Or use extension method.
            writer.WriteLine(string.Format("Span[1]: {0} // 0x{0:X}", span[1]));
            int checkSum = SumExSpan(intSpan); // Implicit conversion ExSpan to ReadOnlyExSpan.
            writer.WriteLine(string.Format("CheckSum: {0} // 0x{0:X}", checkSum));
            writer.WriteLine();
        }

        static void TestExSpan(TextWriter writer, string title, ExSpan<int> span) {
            try {
                // Write.
                writer.WriteLine($"[TestExSpan-{title}]");
                span.Fill(0x01020304);
                span[0] = 0x12345678;
                span[span.Length - 1] = 0x78563412;
                // Read.
                writer.WriteLine(string.Format("Data[0]: {0} // 0x{0:X}", span[0]));
                writer.WriteLine(string.Format("Data[1]: {0} // 0x{0:X}", span[1]));
                writer.WriteLine(string.Format("Data[^1]: {0} // 0x{0:X}", span[span.Length - 1]));
                writer.WriteLine(string.Format("Count(Data[1]): {0} // 0x{0:X}", (long)span.Count(span[1])));
            } catch (Exception ex) {
                writer.WriteLine(string.Format("Run TestExSpan fail! {0}", ex.ToString()));
            }
        }

        static int SumExSpan(ReadOnlyExSpan<int> span) {
            int rt = 0; // Result.
            for (nint i = 0; i < span.Length; i++) {
                rt += span[i];
            }
            return rt;
        }

        static unsafe void Test2GB(TextWriter writer) {
            const nint OutputMaxLength = 8;
            nuint byteSize = 2U * 1024 * 1024 * 1024; // 2GB
            if (IntPtr.Size > sizeof(int)) {
                byteSize += sizeof(int);
            }
            nint bufferSize = (nint)(byteSize / sizeof(int));
            // Create ExSpan by Pointer.
            try {
                void* buffer = ExNativeMemory.Alloc(byteSize);
                try {
                    ExSpan<int> intSpan = new ExSpan<int>(buffer, bufferSize);
                    TestExSpan(writer, "2GB", intSpan);
                    writer.WriteLine(string.Format("ItemsToString: {0}", intSpan.ItemsToString(OutputMaxLength, OutputMaxLength)));
                    writer.WriteLine(string.Format("intSpan.Count(): {0} // 0x{0:X}", (long)intSpan.Count(intSpan[1])));
                    writer.WriteLine(string.Format("intSpan.Length: {0} // 0x{0:X}", (long)intSpan.Length));
                    // Cast to byte.
                    ExSpan<byte> byteSpan = ExMemoryMarshal.Cast<int, byte>(intSpan);
                    writer.WriteLine(string.Format("byteSpan.Length: {0} // 0x{0:X}", (long)byteSpan.Length));
                    writer.WriteLine(string.Format("byteSpan[0]: {0} // 0x{0:X}", byteSpan[0]));
                    writer.WriteLine(string.Format("byteSpan.ItemsToString: {0}", byteSpan.ItemsToString(OutputMaxLength, OutputMaxLength)));
                    writer.WriteLine(string.Format("byteSpan.Count(): {0} // 0x{0:X}", (long)byteSpan.Count(byteSpan[1])));
                    writer.WriteLine();
                } finally {
                    ExNativeMemory.Free(buffer);
                }
            } catch (Exception ex) {
                writer.WriteLine(string.Format("Run Test2GB fail! {0}", ex.ToString()));
            }
        }

    }
}

The example is located in samples/ExSpans.Sample/Program.cs (该范例位于位于 samples/ExSpans.Sample/Program.cs).  This library provides ItemsToString extension methods for types such as ExSpan, which are used to output the values of each elements (本库为 ExSpan 等类型提供了 ItemsToString 扩展方法, 用于输出各个元素的值).  This library also provides ItemsToString extension methods for types such as Span. It can be used by using the Zyl.ExSpans.Extensions.ApplySpans namespace (本库还为 Span 等类型也提供了 ItemsToString 扩展方法. 引用 Zyl.ExSpans.Extensions.ApplySpans 命名空间后便可使用它).

using Zyl.ExSpans.Extensions.ApplySpans;

For details, see tests/ExSpans.Tests/Extensions/ApplySpans/ApplySpanCoreExtensionsTest.cs.

Output results (输出结果)

[TestExSpan-Array]
Data[0]: 305419896 // 0x12345678
Data[1]: 16909060 // 0x1020304
Data[^1]: 2018915346 // 0x78563412
Count(Data[1]): 14 // 0xE

[TestExSpan-Span]
Data[0]: 305419896 // 0x12345678
Data[1]: 16909060 // 0x1020304
Data[^1]: 2018915346 // 0x78563412
Count(Data[1]): 14 // 0xE
Span[1]: 16909060 // 0x1020304
CheckSum: -1733905214 // 0x98A6B4C2

[TestExSpan-2GB]
Data[0]: 305419896 // 0x12345678
Data[1]: 16909060 // 0x1020304
Data[^1]: 2018915346 // 0x78563412
Count(Data[1]): 536870911 // 0x1FFFFFFF
ItemsToString: ExSpan<int>[536870913]{305419896, 16909060, 16909060, 16909060, 16909060, 16909060, 16909060, 16909060, ..., 16909060, 16909060, 16909060, 16909060, 16909060, 16909060, 16909060, 2018915346}
intSpan.Count(): 536870911 // 0x1FFFFFFF
intSpan.Length: 536870913 // 0x20000001
byteSpan.Length: 2147483652 // 0x80000004
byteSpan[0]: 120 // 0x78
byteSpan.ItemsToString: ExSpan<byte>[2147483652]{120, 86, 52, 18, 4, 3, 2, 1, ..., 4, 3, 2, 1, 18, 52, 86, 120}
byteSpan.Count(): 2 // 0x2

Manipulating Memory Mapped Files with ExSpan (使用 ExSpan 操作内存映射文件)

Because of the cumbersome way of manipulating data in memory-mapped files, I had hoped to use Span to manipulate memory-mapped files. However, memory-mapped files use 64-bit index, and Span's 32-bit index is not sufficient (由于内存映射文件的数据操作方法用起来比较繁琐, 曾经希望能用 Span 来操作内存映射文件. 但内存映射文件用了 64位索引, Span的32索引力不从心).

Now ExSpan uses a range of nint index, which are 64-bit on 64-bit operating systems, and are very suitable for memory-mapped files with 64-bit index (现在 ExSpan 使用 nint 索引的范围, 在64位操作系统上是64位的, 非常适合64位索引的内存映射文件).

And this library also provides SafeBufferSpanProvider type to simplify this operation (而且本库还提供了 SafeBufferSpanProvider 类型来简化这一操作).

  1. Use CreateSpanProvider extension method to create SafeBufferSpanProvider based on SafeMemoryMappedViewHandle of memory mapped file (使用 CreateSpanProvider 扩展方法, 基于 内存映射文件的SafeMemoryMappedViewHandle 来创建 SafeBufferSpanProvider).
  2. SafeBufferSpanProvider supports using statement, which can automatically manage the release of unmanaged data (SafeBufferSpanProvider 支持 using 语句, 能自动管理非托管数据的释放).
  3. SafeBufferSpanProvider's CreateExSpan method can be used to create ExSpan (SafeBufferSpanProvider 的 CreateExSpan 方法可以用来创建 ExSpan).

Code of the program (程序的代码)

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using Zyl.ExSpans;

namespace Zyl.ExSpans.Sample {
    internal class ATestMemoryMappedFile {

        static void Main(string[] args) {
            TextWriter writer = Console.Out;

            // Test some.
            TestMemoryMappedFile(writer);
        }

        internal static void TestMemoryMappedFile(TextWriter writer) {
            try {
                const string MemoryMappedFilePath = "ExSpans.Sample.tmp";
                const string? MemoryMappedFileMapName = null; // If it is not null, MacOS will throw an exception. System.PlatformNotSupportedException: Named maps are not supported.
                const long MemoryMappedFileSize = 1 * 1024 * 1024; // 1MB
                using MemoryMappedFile mappedFile = MemoryMappedFile.CreateFromFile(MemoryMappedFilePath, FileMode.Create, MemoryMappedFileMapName, MemoryMappedFileSize);
                using MemoryMappedViewAccessor accessor = mappedFile.CreateViewAccessor();
                using SafeBufferSpanProvider spanProvider = accessor.SafeMemoryMappedViewHandle.CreateSpanProvider();
                // Write.
                writer.WriteLine("[TestMemoryMappedFile]");
                ExSpan<int> spanInt = spanProvider.CreateExSpan<int>();
                spanInt.Fill(0x01020304);
                spanInt[0] = 0x12345678;
                // Read.
                writer.WriteLine(string.Format("Data[0]: {0} // 0x{0:X}", spanInt[0]));
                writer.WriteLine(string.Format("Data[1]: {0} // 0x{0:X}", spanInt[1]));
                // Extension methods provided by ExSpanExtensions.
                writer.WriteLine(string.Format("ItemsToString: {0}", spanProvider.ItemsToString(spanProvider.GetPinnableReadOnlyReference(), 16)));
                // done.
                writer.WriteLine();
            } catch (Exception ex) {
                writer.WriteLine(string.Format("Run TestMemoryMappedFile fail! {0}", ex.ToString()));
            }
        }
    }
}

The example is located in samples/ExSpans.Sample/ATestMemoryMappedFile.cs (该范例位于位于 samples/ExSpans.Sample/ATestMemoryMappedFile.cs).

Note: SafeBufferSpanProvider also supports ItemsToString extension method. Before .NET 9, the spanProvider.GetPinnableReadOnlyReference() parameter had to be passed; since .NET 9, this parameter can be omitted (注: SafeBufferSpanProvider 也支持 ItemsToString 扩展方法. 在 .NET 9 以前, 需传递 spanProvider.GetPinnableReadOnlyReference() 参数; 而从 .NET 9 开始, 可省略该参数).

Output results (输出结果)

[TestMemoryMappedFile]
Data[0]: 305419896 // 0x12345678
Data[1]: 16909060 // 0x1020304
ItemsToString: SafeBufferSpanProvider[1048576]{120, 86, 52, 18, 4, 3, 2, 1, 4, 3, 2, 1, 4, 3, 2, 1, ...}

Benchmark (基准测试)

From Starting from NET 7, ExSpan's performance is the same as Span The following benchmark tests will prove this assertion (从 .NET 7 开始, ExSpan 的性能与 Span 相同. 下面的基准测试将证明这一论断).

Source code for benchmark (基准测试的源代码)

 The following is the source code for benchmark ExSpan as an array summing application (下面将以数组求和为例, 来对 ExSpan 编写基准测试代码).  The benchmark tool used is BenchmarkDotNet (测试工具用的是 BenchmarkDotNet). 

StaticSumForArray: Summation using index access to arrays (使用索引访问数组实现求和)

First, use the array summing method as a baseline (首先, 以数组求和的方法作为 baseline).

using TMy = Int32;

public static TMy StaticSumForArray(TMy[] src, int srcCount) {
    TMy rt = 0; // Result.
    for (int i = 0; i < srcCount; ++i) {
        rt += src[i];
    }
    return rt;
}

[Benchmark(Baseline = true)]
public void SumForArray() {
    dstTMy = StaticSumForArray(srcArray, srcArray.Length);
}

The srcArray is a pre-allocated array (srcArray 是预先分配好的数组).

The dstTMy is a global variable, in order to avoid erasing the SumForArray method during compilation optimization (dstTMy 是一个全局变量, 为了避免编译优化时抹掉 SumForArray 方法).

Summation using index access to Span (使用索引访问 Span 实现求和)

Summation using index access to Span (该方法使用索引访问 Span 实现求和).

public static TMy StaticSumForSpan(TMy[] src, int srcCount) {
    TMy rt = 0; // Result.
    Span<TMy> span = new Span<TMy>(src, 0, srcCount);
    for (int i = 0; i < srcCount; ++i) {
        rt += span[i];
    }
    return rt;
}

[Benchmark]
public void SumForSpan() {
    dstTMy = StaticSumForSpan(srcArray, srcArray.Length);
}

SumForExSpan: Summation using index access to ExSpan (使用索引访问 ExSpan 实现求和)

Simply change Span to ExSpan and change the index type from int to nint, and you're done (仅需将 Span 改为 ExSpan, 再将索引类型从 int 改为 nint, 便完成了改造)!

public static TMy StaticSumForExSpan(TMy[] src, int srcCount) {
    TMy rt = 0; // Result.
    nint srcCountCur = srcCount;
    ExSpan<TMy> span = new ExSpan<TMy>(src, 0, srcCountCur);
    for (nint i = 0; i < srcCountCur; ++i) {
        rt += span[i];
    }
    return rt;
}

[Benchmark]
public void SumForExSpan() {
    dstTMy = StaticSumForExSpan(srcArray, srcArray.Length);
}

Other methods (其他方法)

In addition to the methods introduced above, there are also the following methods (除了上面介绍的方法外, 还有以下方法).

  • SumForPtr: Summation using native pointer access to arrays (使用原生指针访问数组实现求和).
  • SumForExSpanByPtr: Summation using index access to ExSpan created by pointer (使用索引访问 指针创建的ExSpan 实现求和).
  • SumForExSpanUsePtr: Summation using native pointer access to ExSpan (使用原生指针访问 ExSpan 实现求和).
  • SumForExSpanUseRef: Summation using managed pointer(ref) access to ExSpan (使用 托管指针(ref) 访问 ExSpan 实现求和).

For details, see tests/ExSpans.Benchmarks.Inc/AExSpan/SumBenchmark_Int32.cs.

Benchmarks of X86 architecture

.NET 7

BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351)
AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.301
  [Host]    : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
  MediumRun : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2

| Method             | N      | Mean     | Error    | StdDev   | Ratio | Code Size |
|------------------- |------- |---------:|---------:|---------:|------:|----------:|
| SumForArray        | 262144 | 60.40 us | 0.234 us | 0.335 us |  1.00 |     500 B |
| SumForPtr          | 262144 | 58.54 us | 0.173 us | 0.258 us |  0.97 |     145 B |
| SumForSpan         | 262144 | 58.49 us | 0.199 us | 0.297 us |  0.97 |     186 B |
| SumForExSpan       | 262144 | 58.47 us | 0.208 us | 0.305 us |  0.97 |     205 B |
| SumForExSpanByPtr  | 262144 | 58.01 us | 0.099 us | 0.135 us |  0.96 |     187 B |
| SumForExSpanUsePtr | 262144 | 58.49 us | 0.121 us | 0.177 us |  0.97 |     174 B |
| SumForExSpanUseRef | 262144 | 58.72 us | 0.164 us | 0.245 us |  0.97 |     159 B |

It can be seen that the performance of ExSpan is the same as Span (可见, ExSpan 的性能与 Span 相同).

.NET 6

AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.301
  [Host]    : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2
  MediumRun : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2

| Method             | N      | Mean      | Error    | StdDev   | Ratio | RatioSD | Code Size |
|------------------- |------- |----------:|---------:|---------:|------:|--------:|----------:|
| SumForArray        | 262144 |  68.11 us | 0.207 us | 0.311 us |  1.00 |    0.01 |   1,600 B |
| SumForPtr          | 262144 |  58.67 us | 0.157 us | 0.231 us |  0.86 |    0.01 |     147 B |
| SumForSpan         | 262144 |  60.25 us | 0.176 us | 0.264 us |  0.88 |    0.01 |     206 B |
| SumForExSpan       | 262144 | 165.34 us | 0.777 us | 1.162 us |  2.43 |    0.02 |     715 B |
| SumForExSpanByPtr  | 262144 | 171.65 us | 0.705 us | 1.055 us |  2.52 |    0.02 |     331 B |
| SumForExSpanUsePtr | 262144 |  59.83 us | 0.334 us | 0.500 us |  0.88 |    0.01 |     676 B |
| SumForExSpanUseRef | 262144 |  61.23 us | 0.599 us | 0.859 us |  0.90 |    0.01 |     663 B |

As you can see, before .NET 7, ExSpan's performance was a bit slower than Span (可见, 在 .NET 7 之前, ExSpan 的性能比 Span 慢一些). 

.NET Framework

BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351)
AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
  [Host]    : .NET Framework 4.8.1 (4.8.9300.0), X64 RyuJIT VectorSize=256
  MediumRun : .NET Framework 4.8.1 (4.8.9300.0), X64 RyuJIT VectorSize=256

| Method             | N      | Mean      | Error    | StdDev    | Ratio | RatioSD | Code Size |
|------------------- |------- |----------:|---------:|----------:|------:|--------:|----------:|
| SumForArray        | 262144 |  69.20 us | 0.268 us |  0.376 us |  1.00 |    0.01 |   6,943 B |
| SumForPtr          | 262144 |  58.80 us | 0.131 us |  0.193 us |  0.85 |    0.01 |     154 B |
| SumForSpan         | 262144 | 122.44 us | 0.437 us |  0.640 us |  1.77 |    0.01 |     250 B |
| SumForExSpan       | 262144 | 562.53 us | 8.190 us | 12.259 us |  8.13 |    0.18 |     584 B |
| SumForExSpanByPtr  | 262144 | 219.07 us | 0.659 us |  0.965 us |  3.17 |    0.02 |     381 B |
| SumForExSpanUsePtr | 262144 |  58.61 us | 0.194 us |  0.285 us |  0.85 |    0.01 |     635 B |
| SumForExSpanUseRef | 262144 |  58.72 us | 0.187 us |  0.279 us |  0.85 |    0.01 |     614 B |

ExSpan can also run in the NET Framework, but it is slower (ExSpan在 .NET Framework 中也能运行, 只是更慢一些).

There is a way around this performance issue - use ExSpan only as a passing parameter, and then use pointers to manipulate the data. See SumForExSpanUsePtr or SumForExSpanUseRef, both of which are faster than SumForArray/SumForSpan. This library's ExMemoryExtensions and other types are optimized in this way (有一种办法可以解决这种性能问题——仅将 ExSpan 用做传参, 随后用指针进行数据处理. 可参考 SumForExSpanUsePtr 或 SumForExSpanUseRef, 它们都比 SumForArray/SumForSpan 快. 本库的 ExMemoryExtensions 等类型, 就是用这个办法进行优化的).

Benchmarks of Arm architecture

.NET 7

BenchmarkDotNet v0.14.0, macOS Sequoia 15.5 (24F74) [Darwin 24.5.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.102
  [Host]    : .NET 7.0.20 (7.0.2024.26716), Arm64 RyuJIT AdvSIMD
  MediumRun : .NET 7.0.20 (7.0.2024.26716), Arm64 RyuJIT AdvSIMD

| Method             | N      | Mean      | Error    | StdDev   | Ratio | RatioSD |
|------------------- |------- |----------:|---------:|---------:|------:|--------:|
| SumForArray        | 262144 |  95.02 us | 0.303 us | 0.444 us |  1.00 |    0.01 |
| SumForPtr          | 262144 |  93.60 us | 3.269 us | 4.893 us |  0.99 |    0.05 |
| SumForSpan         | 262144 |  96.30 us | 1.313 us | 1.966 us |  1.01 |    0.02 |
| SumForExSpan       | 262144 | 121.74 us | 0.064 us | 0.092 us |  1.28 |    0.01 |
| SumForExSpanByPtr  | 262144 | 122.65 us | 0.488 us | 0.715 us |  1.29 |    0.01 |
| SumForExSpanUsePtr | 262144 |  88.76 us | 0.596 us | 0.873 us |  0.93 |    0.01 |
| SumForExSpanUseRef | 262144 |  89.04 us | 0.338 us | 0.506 us |  0.94 |    0.01 |

It can be seen that the performance of ExSpan is very similar to Span, slightly slower by about 26% (可见, ExSpan 的性能与 Span 很接近, 慢了 (121.74 / 96.30 - 1 =) 26% 左右).

.NET 9

BenchmarkDotNet v0.14.0, macOS Sequoia 15.5 (24F74) [Darwin 24.5.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.102
  [Host]    : .NET 9.0.1 (9.0.124.61010), Arm64 RyuJIT AdvSIMD
  MediumRun : .NET 9.0.1 (9.0.124.61010), Arm64 RyuJIT AdvSIMD

| Method             | N      | Mean      | Error    | StdDev   | Ratio |
|------------------- |------- |----------:|---------:|---------:|------:|
| SumForArray        | 262144 |  86.25 us | 0.069 us | 0.103 us |  1.00 |
| SumForPtr          | 262144 |  76.78 us | 0.335 us | 0.492 us |  0.89 |
| SumForSpan         | 262144 |  93.34 us | 0.238 us | 0.326 us |  1.08 |
| SumForExSpan       | 262144 | 104.89 us | 0.087 us | 0.131 us |  1.22 |
| SumForExSpanByPtr  | 262144 | 104.72 us | 0.072 us | 0.105 us |  1.21 |
| SumForExSpanUsePtr | 262144 |  78.05 us | 0.841 us | 1.259 us |  0.90 |
| SumForExSpanUseRef | 262144 |  78.02 us | 0.854 us | 1.252 us |  0.90 |

The performance of .NET 9 has made progress again, and the performance of ExSpan is very close to Span The difference is only about 12% (.NET 9 时性能又有进步, ExSpan 的性能与 Span 很接近了. 仅相差 (104.89 / 93.34 - 1 =) 12% 左右).

If you want to pursue optimal performance, you can also use pointers for optimization You can refer to SumForExSpanUsePtr or SumForExSpanUseRef, both of which are faster than SumForSpan (若想追求最佳性能, 也可利用指针进行优化. 可参考 SumForExSpanUsePtr 或 SumForExSpanUseRef, 它们都比 SumForSpan 快).

Documentation (文档)

ChangeLog (变更日志)

Full list: ChangeLog