
发布 ExSpans v1.0, 它突破了32位索引的限制, 提供了 nint 索引范围Span类型;并能使内存映射文件支持 Span操作
源代码: https://github.com/zyl910/ExSpans
ExSpans: Extended spans of nint index range (nint 索引范围的扩展跨度).
Package | Nuget | Description |
---|---|---|
ExSpans |
Extended spans of nint index range (nint 索引范围的扩展跨度). Commonly types: ExMemoryExtensions , ExNativeMemory . |
|
ExSpans.Core |
Extended spans of nint index range – Core type (nint 索引范围的扩展跨度 – 核心类型). Commonly types: ExSpan<T> , ReadOnlyExSpan<T> , ExMemoryMarshal , SafeBufferSpanProvider . |
用途
Span 是 C# 7.2 引入的一种新结构, 允许开发者以类型安全的方式访问任意内存的连续区域. 它既可以用于托管内存(如数组), 又可以用于非托管内存(如通过 Marshal.AllocHGlobal
分配的内存), 并且不需要进行内存复制, 从而提高性能.
然而 Span 存在一个局限性, 它使用的是 int (Int32: 32位整数) 类型的索引. 即使是在 64位操作系统中, 它仅能访问最长 2G(2^31
) 的数据. 而 Marshal.AllocHGlobal
方法在分配内存时支持 nint (IntPtr: 原生整数) 类型的长度, 在 64位系统上能分配超过 2GB 的非托管内存, Span 难以支持这么长的数据. 在没有 Span 的时候, 手动操作非托管内存是非常繁琐的, 而且代码的通用性不高.
ExSpan 解决了这一局限性, 它使用 nint 类型的索引. nint 类型的字节大小, 与原生指针完全相同, 故在64位系统上能以64位的索引来访问数据. ExSpan 的用法与 Span 完全相同, 且像 Span 那样提供了大量的工具函数. 这使得它适用于 图像处理、视频处理、深度学习等大规模数据的领域.
ExSpan 继承了Span 的优点:
- 零分配. ExSpan 是一个零分配的表示形式, 意味着它不会在堆上分配内存, 而是分配在栈上, 这样可以减少垃圾回收的负担.
- 安全性. ExSpan 提供了安全的内存访问, 避免了指针操作带来的风险, 如缓冲区溢出和空指针访问.
- 通用性. 既可以用于托管内存(如数组), 又可以用于非托管内存(如通过
Marshal.AllocHGlobal
分配的内存). - 切片功能. ExSpan 支持切片操作, 可以轻松创建指向数组或内存块某一部分的 ExSpan, 而无需复制数据.
- 高性能. 由于 ExSpan 的设计, 操作它的性能接近于直接操作数组. 使其适合高性能应用场景, 如 缓冲区数据处理、字符串解析、图像处理 等.
- 功能丰富. 像 Span 那样提供了大量的工具函数. 例如用 ExMemoryMarshal 替代 MemoryMarshal, 用 ExMemoryExtensions 替代 MemoryExtensions, 用 ExNativeMemory 替代 NativeMemory. 还提供了 SafeBufferSpanProvider 等类. 且它们利用 VectorTraits 库, 实现了跨平台的SIMD硬件加速.
本库还具有这些优点:
- 支持多种 .NET版本. 从 .NET Framework 4.5, 到最新的 .NET 9, 全都支持. 而且支持 .NET Standard 1.1 ~ .NET Standard 2.1 .
- 移植新版本的功能. 能给早期版本的.NET, 提供最新的 Span功能. 例如 .NET 6.0 新增的
MemoryExtensions.TryWrite
方法. - 跨平台. 它完全由托管代码所组成, 能够支持 Windows, Linux, MacOS, iOS, Android, Wasm 等平台. 能避免繁琐的“根据当前平台选择不同的原生dll”工作.
- 支持原生AOT. 当需要时, 可以利用原生AOT技术, 将程序编译为目标平台的原生代码(机器码). 此时不再需要 .NET 运行时, 且具有启动速度快等优点.
入门指南
1) 通过NuGet安装本库
可以使用“包管理器”GUI来安装本库. 或可在“包管理器控制台”里输入以下命令进行安装.
NuGet: PM> Install-Package ExSpans
2) 简单范例
一个计算校验和的函数
首先, 我们用 ReadOnlySpan 实现一个计算校验和的函数,
static int SumSpan(ReadOnlySpan<int> span) {
int rt = 0; // Result.
for (int i = 0; i < span.Length; i++) {
rt += span[i];
}
return rt;
}
随后可以用 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;
}
完整程序的代码
ExSpan(或 ReadOnlyExSpan) 的用法, 与 Span(或 ReadOnlySpan) 完全相同, 仅是索引类型从 int 改为了 nint .
本库像 Span 那样提供了大量的工具函数. 例如用 ExMemoryMarshal 替代 MemoryMarshal, 用 ExMemoryExtensions 替代 MemoryExtensions, 用 ExNativeMemory 替代 NativeMemory. 后面范例代码中使用的 Count 方法, 是 ExMemoryExtensions 里的扩展方法.
使用类型转换运算符, 或是 AsSpan/AsExSpan 等扩展方法, 可以方便的将 ExSpan(或 ReadOnlyExSpan) 与 Span(或 ReadOnlySpan) 进行类型转换.
下面展示了各种用法.
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()));
}
}
}
}
该范例位于位于 samples/ExSpans.Sample/Program.cs
.
本库为 ExSpan 等类型提供了 ItemsToString 扩展方法, 用于输出各个元素的值.
本库还为 Span 等类型也提供了 ItemsToString 扩展方法. 引用 Zyl.ExSpans.Extensions.ApplySpans
命名空间后便可使用它.
using Zyl.ExSpans.Extensions.ApplySpans;
详见 tests/ExSpans.Tests/Extensions/ApplySpans/ApplySpanCoreExtensionsTest.cs
.
输出结果
[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
使用 ExSpan 操作内存映射文件
由于内存映射文件的数据操作方法用起来比较繁琐, 曾经希望能用 Span 来操作内存映射文件. 但内存映射文件用了 64位索引, Span的32索引力不从心.
现在 ExSpan 使用 nint 索引的范围, 在64位操作系统上是64位的, 非常适合64位索引的内存映射文件.
而且本库还提供了 SafeBufferSpanProvider 类来简化这一操作.
- 使用 CreateSpanProvider 扩展方法, 基于 内存映射文件的SafeMemoryMappedViewHandle 来创建 SafeBufferSpanProvider .
- SafeBufferSpanProvider 支持 using 语句, 能自动管理非托管数据的释放.
- SafeBufferSpanProvider 的 CreateExSpan 方法可以用来创建 ExSpan .
程序的代码
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()));
}
}
}
}
该范例位于 samples/ExSpans.Sample/ATestMemoryMappedFile.cs
.
注: SafeBufferSpanProvider 也支持 ItemsToString 扩展方法. 在 .NET 9 以前, 需传递 spanProvider.GetPinnableReadOnlyReference()
参数; 而从 .NET 9 开始, 可省略该参数.
输出结果
[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, ...}
基准测试
从 .NET 7 开始, ExSpan 的性能与 Span 相同. 下面的基准测试将证明这一论断.
基准测试的源代码
下面将以数组求和为例, 来对 ExSpan 编写基准测试代码.
测试工具用的是 BenchmarkDotNet .
StaticSumForArray: Summation using index access to arrays (使用索引访问数组实现求和)
首先, 以数组求和的方法作为 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);
}
srcArray 是预先分配好的数组.
dstTMy 是一个全局变量, 为了避免编译优化时抹掉 SumForArray 方法.
Summation using index access to Span (使用索引访问 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 实现求和)
仅需将 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);
}
其他方法
除了上面介绍的方法外, 还有以下方法.
- 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 实现求和).
详见 tests/ExSpans.Benchmarks.Inc/AExSpan/SumBenchmark_Int32.cs
.
X86架构的基准测试
.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 |
可见, 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 |
可见, 在 .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在 .NET Framework 中也能运行, 只是更慢一些.
有一种办法可以解决这种性能问题——仅将 ExSpan 用做传参, 随后用指针进行数据处理. 可参考 SumForExSpanUsePtr 或 SumForExSpanUseRef, 它们都比 SumForArray/SumForSpan 快. 本库的 ExMemoryExtensions 等类型, 就是用这个办法进行优化的.
Arm架构的基准测试
.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 |
可见, 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 |
.NET 9 时性能又有进步, ExSpan 的性能与 Span 很接近了. 仅相差 (104.89 / 93.34 – 1 =) 12% 左右.
若想追求最佳性能, 也可利用指针进行优化. 可参考 SumForExSpanUsePtr 或 SumForExSpanUseRef, 它们都比 SumForSpan 快.
文档
- Online document: https://zyl910.github.io/ExSpans_doc/
- DocFX: Run
docfx_serve.bat
. Then browse http://localhost:8080/ .