发布 ExSpans v1.0, 它突破了32位索引的限制, 提供了 nint 索引范围Span类型;并能使内存映射文件支持 Span操作
<h1 id="发布-exspans-v10-它突破了32位索引的限制-提供了-nint-索引范围span类型并能使内存映射文件支持-span操作">发布 ExSpans v1.0, 它突破了32位索引的限制, 提供了 nint 索引范围Span类型;并能使内存映射文件支持 Span操作</h1><p>源代码: https://github.com/zyl910/ExSpans</p>
<p>ExSpans: Extended spans of nint index range (nint 索引范围的扩展跨度).</p>
<table>
<thead>
<tr>
<th>Package</th>
<th>Nuget</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ExSpans</code></td>
<td><img src="https://img.shields.io/nuget/v/ExSpans" alt="ExSpans" loading="lazy"></td>
<td>Extended spans of nint index range (nint 索引范围的扩展跨度). Commonly types: <code>ExMemoryExtensions</code>, <code>ExNativeMemory</code>.</td>
</tr>
<tr>
<td><code>ExSpans.Core</code></td>
<td><img src="https://img.shields.io/nuget/v/ExSpans.Core" alt="ExSpans" loading="lazy"></td>
<td>Extended spans of nint index range - Core type (nint 索引范围的扩展跨度 - 核心类型). Commonly types: <code>ExSpan<T></code>, <code>ReadOnlyExSpan<T></code>, <code>ExMemoryMarshal</code>, <code>SafeBufferSpanProvider</code>.</td>
</tr>
</tbody>
</table>
<h2 id="用途">用途</h2>
<p>Span 是 C# 7.2 引入的一种新结构, 允许开发者以类型安全的方式访问任意内存的连续区域. 它既可以用于托管内存(如数组), 又可以用于非托管内存(如通过 <code>Marshal.AllocHGlobal</code> 分配的内存), 并且不需要进行内存复制, 从而提高性能.</p>
<p>然而 Span 存在一个局限性, 它使用的是 int (Int32: 32位整数) 类型的索引. 即使是在 64位操作系统中, 它仅能访问最长 2G(<code>2^31</code>) 的数据. 而 <code>Marshal.AllocHGlobal</code> 方法在分配内存时支持 nint (IntPtr: 原生整数) 类型的长度, 在 64位系统上能分配超过 2GB 的非托管内存, Span 难以支持这么长的数据. 在没有 Span 的时候, 手动操作非托管内存是非常繁琐的, 而且代码的通用性不高.</p>
<p>ExSpan 解决了这一局限性, 它使用 nint 类型的索引. nint 类型的字节大小, 与原生指针完全相同, 故在64位系统上能以64位的索引来访问数据. ExSpan 的用法与 Span 完全相同, 且像 Span 那样提供了大量的工具函数. 这使得它适用于 图像处理、视频处理、深度学习等大规模数据的领域.</p>
<p>ExSpan 继承了Span 的优点:</p>
<ul>
<li><strong>零分配</strong>. ExSpan 是一个零分配的表示形式, 意味着它不会在堆上分配内存, 而是分配在栈上, 这样可以减少垃圾回收的负担.</li>
<li><strong>安全性</strong>. ExSpan 提供了安全的内存访问, 避免了指针操作带来的风险, 如缓冲区溢出和空指针访问.</li>
<li><strong>通用性</strong>. 既可以用于托管内存(如数组), 又可以用于非托管内存(如通过 <code>Marshal.AllocHGlobal</code> 分配的内存).</li>
<li><strong>切片功能</strong>. ExSpan 支持切片操作, 可以轻松创建指向数组或内存块某一部分的 ExSpan, 而无需复制数据.</li>
<li><strong>高性能</strong>. 由于 ExSpan 的设计, 操作它的性能接近于直接操作数组. 使其适合高性能应用场景, 如 缓冲区数据处理、字符串解析、图像处理 等.</li>
<li><strong>功能丰富</strong>. 像 Span 那样提供了大量的工具函数. 例如用 ExMemoryMarshal 替代 MemoryMarshal, 用 ExMemoryExtensions 替代 MemoryExtensions, 用 ExNativeMemory 替代 NativeMemory. 还提供了 SafeBufferSpanProvider 等类. 且它们利用 VectorTraits 库, 实现了跨平台的SIMD硬件加速.</li>
</ul>
<p>本库还具有这些优点:</p>
<ul>
<li><strong>支持多种 .NET版本</strong>. 从 .NET Framework 4.5, 到最新的 .NET 9, 全都支持. 而且支持 .NET Standard 1.1 ~ .NET Standard 2.1 .</li>
<li><strong>移植新版本的功能</strong>. 能给早期版本的.NET, 提供最新的 Span功能. 例如 .NET 6.0 新增的 <code>MemoryExtensions.TryWrite</code> 方法.</li>
<li><strong>跨平台</strong>. 它完全由托管代码所组成, 能够支持 Windows, Linux, MacOS, iOS, Android, Wasm 等平台. 能避免繁琐的“根据当前平台选择不同的原生dll”工作.</li>
<li><strong>支持原生AOT</strong>. 当需要时, 可以利用原生AOT技术, 将程序编译为目标平台的原生代码(机器码). 此时不再需要 .NET 运行时, 且具有启动速度快等优点.</li>
</ul>
<h2 id="入门指南">入门指南</h2>
<h3 id="1-通过nuget安装本库">1) 通过NuGet安装本库</h3>
<p>可以使用“包管理器”GUI来安装本库. 或可在“包管理器控制台”里输入以下命令进行安装.</p>
<p>NuGet: <code>PM> Install-Package ExSpans</code></p>
<h3 id="2-简单范例">2) 简单范例</h3>
<h4 id="一个计算校验和的函数">一个计算校验和的函数</h4>
<p>首先, 我们用 ReadOnlySpan 实现一个计算校验和的函数,</p>
<pre><code class="language-cs">static int SumSpan(ReadOnlySpan<int> span) {
int rt = 0; // Result.
for (int i = 0; i < span.Length; i++) {
rt += span;
}
return rt;
}
</code></pre>
<p>随后可以用 ExSpans 库中的 ReadOnlyExSpan 类型来改造这个函数. 仅需将 ReadOnlySpan 改为 ReadOnlyExSpan, 再将索引类型从 int 改为 nint, 便完成了改造.</p>
<pre><code class="language-cs">static int SumExSpan(ReadOnlyExSpan<int> span) {
int rt = 0; // Result.
for (nint i = 0; i < span.Length; i++) {
rt += span;
}
return rt;
}
</code></pre>
<h4 id="完整程序的代码">完整程序的代码</h4>
<p>ExSpan(或 ReadOnlyExSpan) 的用法, 与 Span(或 ReadOnlySpan) 完全相同, 仅是索引类型从 int 改为了 nint .</p>
<p>本库像 Span 那样提供了大量的工具函数. 例如用 ExMemoryMarshal 替代 MemoryMarshal, 用 ExMemoryExtensions 替代 MemoryExtensions, 用 ExNativeMemory 替代 NativeMemory. 后面范例代码中使用的 Count 方法, 是 ExMemoryExtensions 里的扩展方法.</p>
<p>使用类型转换运算符, 或是 AsSpan/AsExSpan 等扩展方法, 可以方便的将 ExSpan(或 ReadOnlyExSpan) 与 Span(或 ReadOnlySpan) 进行类型转换.</p>
<p>下面展示了各种用法.</p>
<pre><code class="language-cs">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;
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;
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: {0} // 0x{0:X}", span));
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($"");
span.Fill(0x01020304);
span = 0x12345678;
span = 0x78563412;
// Read.
writer.WriteLine(string.Format("Data: {0} // 0x{0:X}", span));
writer.WriteLine(string.Format("Data: {0} // 0x{0:X}", span));
writer.WriteLine(string.Format("Data[^1]: {0} // 0x{0:X}", span));
writer.WriteLine(string.Format("Count(Data): {0} // 0x{0:X}", (long)span.Count(span)));
} 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;
}
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)));
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} // 0x{0:X}", byteSpan));
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)));
writer.WriteLine();
} finally {
ExNativeMemory.Free(buffer);
}
} catch (Exception ex) {
writer.WriteLine(string.Format("Run Test2GB fail! {0}", ex.ToString()));
}
}
}
}
</code></pre>
<p>该范例位于位于<code>samples/ExSpans.Sample/Program.cs</code> .</p>
<p>本库为 ExSpan 等类型提供了 ItemsToString 扩展方法, 用于输出各个元素的值.</p>
<p>本库还为 Span 等类型也提供了 ItemsToString 扩展方法. 引用 <code>Zyl.ExSpans.Extensions.ApplySpans</code> 命名空间后便可使用它.</p>
<pre><code>using Zyl.ExSpans.Extensions.ApplySpans;
</code></pre>
<p>详见 <code>tests/ExSpans.Tests/Extensions/ApplySpans/ApplySpanCoreExtensionsTest.cs</code>.</p>
<h4 id="输出结果">输出结果</h4>
<pre><code>
Data: 305419896 // 0x12345678
Data: 16909060 // 0x1020304
Data[^1]: 2018915346 // 0x78563412
Count(Data): 14 // 0xE
Data: 305419896 // 0x12345678
Data: 16909060 // 0x1020304
Data[^1]: 2018915346 // 0x78563412
Count(Data): 14 // 0xE
Span: 16909060 // 0x1020304
CheckSum: -1733905214 // 0x98A6B4C2
Data: 305419896 // 0x12345678
Data: 16909060 // 0x1020304
Data[^1]: 2018915346 // 0x78563412
Count(Data): 536870911 // 0x1FFFFFFF
ItemsToString: ExSpan<int>{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: 120 // 0x78
byteSpan.ItemsToString: ExSpan<byte>{120, 86, 52, 18, 4, 3, 2, 1, ..., 4, 3, 2, 1, 18, 52, 86, 120}
byteSpan.Count(): 2 // 0x2
</code></pre>
<h3 id="使用-exspan-操作内存映射文件">使用 ExSpan 操作内存映射文件</h3>
<p>由于内存映射文件的数据操作方法用起来比较繁琐, 曾经希望能用 Span 来操作内存映射文件. 但内存映射文件用了 64位索引, Span的32索引力不从心.</p>
<p>现在 ExSpan 使用 nint 索引的范围, 在64位操作系统上是64位的, 非常适合64位索引的内存映射文件.</p>
<p>而且本库还提供了 SafeBufferSpanProvider 类来简化这一操作.</p>
<ol>
<li>使用 CreateSpanProvider 扩展方法, 基于 内存映射文件的SafeMemoryMappedViewHandle 来创建 SafeBufferSpanProvider .</li>
<li>SafeBufferSpanProvider 支持 using 语句, 能自动管理非托管数据的释放.</li>
<li>SafeBufferSpanProvider 的 CreateExSpan 方法可以用来创建 ExSpan .</li>
</ol>
<h4 id="程序的代码">程序的代码</h4>
<pre><code class="language-cs">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("");
ExSpan<int> spanInt = spanProvider.CreateExSpan<int>();
spanInt.Fill(0x01020304);
spanInt = 0x12345678;
// Read.
writer.WriteLine(string.Format("Data: {0} // 0x{0:X}", spanInt));
writer.WriteLine(string.Format("Data: {0} // 0x{0:X}", spanInt));
// 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()));
}
}
}
}
</code></pre>
<p>该范例位于 <code>samples/ExSpans.Sample/ATestMemoryMappedFile.cs</code> .</p>
<p>注: SafeBufferSpanProvider 也支持 ItemsToString 扩展方法. 在 .NET 9 以前, 需传递 <code>spanProvider.GetPinnableReadOnlyReference()</code> 参数; 而从 .NET 9 开始, 可省略该参数.</p>
<h4 id="输出结果-1">输出结果</h4>
<pre><code>
Data: 305419896 // 0x12345678
Data: 16909060 // 0x1020304
ItemsToString: SafeBufferSpanProvider{120, 86, 52, 18, 4, 3, 2, 1, 4, 3, 2, 1, 4, 3, 2, 1, ...}
</code></pre>
<h2 id="基准测试">基准测试</h2>
<p>从 .NET 7 开始, ExSpan 的性能与 Span 相同. 下面的基准测试将证明这一论断.</p>
<h3 id="基准测试的源代码">基准测试的源代码</h3>
<p>下面将以数组求和为例, 来对 ExSpan 编写基准测试代码.</p>
<p>测试工具用的是 BenchmarkDotNet .</p>
<h4 id="staticsumforarray-summation-using-index-access-to-arrays-使用索引访问数组实现求和">StaticSumForArray: Summation using index access to arrays (使用索引访问数组实现求和)</h4>
<p>首先, 以数组求和的方法作为 baseline.</p>
<pre><code class="language-cs">using TMy = Int32;
public static TMy StaticSumForArray(TMy[] src, int srcCount) {
TMy rt = 0; // Result.
for (int i = 0; i < srcCount; ++i) {
rt += src;
}
return rt;
}
public void SumForArray() {
dstTMy = StaticSumForArray(srcArray, srcArray.Length);
}
</code></pre>
<p>srcArray 是预先分配好的数组.</p>
<p>dstTMy 是一个全局变量, 为了避免编译优化时抹掉 SumForArray 方法.</p>
<h4 id="summation-using-index-access-to-span-使用索引访问-span-实现求和">Summation using index access to Span (使用索引访问 Span 实现求和)</h4>
<p>该方法使用索引访问 Span 实现求和.</p>
<pre><code class="language-cs">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;
}
return rt;
}
public void SumForSpan() {
dstTMy = StaticSumForSpan(srcArray, srcArray.Length);
}
</code></pre>
<h4 id="sumforexspan-summation-using-index-access-to-exspan-使用索引访问-exspan-实现求和">SumForExSpan: Summation using index access to ExSpan (使用索引访问 ExSpan 实现求和)</h4>
<p>仅需将 Span 改为 ExSpan, 再将索引类型从 int 改为 nint, 便完成了改造.</p>
<pre><code class="language-cs">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;
}
return rt;
}
public void SumForExSpan() {
dstTMy = StaticSumForExSpan(srcArray, srcArray.Length);
}
</code></pre>
<h4 id="其他方法">其他方法</h4>
<p>除了上面介绍的方法外, 还有以下方法.</p>
<ul>
<li>SumForPtr: Summation using native pointer access to arrays (使用原生指针访问数组实现求和).</li>
<li>SumForExSpanByPtr: Summation using index access to ExSpan created by pointer (使用索引访问 指针创建的ExSpan 实现求和).</li>
<li>SumForExSpanUsePtr: Summation using native pointer access to ExSpan (使用原生指针访问 ExSpan 实现求和).</li>
<li>SumForExSpanUseRef: Summation using managed pointer(ref) access to ExSpan (使用 托管指针(ref) 访问 ExSpan 实现求和).</li>
</ul>
<p>详见 <code>tests/ExSpans.Benchmarks.Inc/AExSpan/SumBenchmark_Int32.cs</code>.</p>
<h3 id="x86架构的基准测试">X86架构的基准测试</h3>
<h4 id="net-7">.NET 7</h4>
<pre><code>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
: .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 |
</code></pre>
<p>可见, ExSpan 的性能与 Span 相同.</p>
<h4 id="net-6">.NET 6</h4>
<pre><code>AMD Ryzen 7 7840H w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.301
: .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 |
</code></pre>
<p>可见, 在 .NET 7 之前, ExSpan 的性能比 Span 慢一些.</p>
<h4 id="net-framework">.NET Framework</h4>
<pre><code>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 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 |
</code></pre>
<p>ExSpan在 .NET Framework 中也能运行, 只是更慢一些.</p>
<p>有一种办法可以解决这种性能问题——仅将 ExSpan 用做传参, 随后用指针进行数据处理. 可参考 SumForExSpanUsePtr 或 SumForExSpanUseRef, 它们都比 SumForArray/SumForSpan 快. 本库的 ExMemoryExtensions 等类型, 就是用这个办法进行优化的.</p>
<h3 id="arm架构的基准测试">Arm架构的基准测试</h3>
<h4 id="net-7-1">.NET 7</h4>
<pre><code>BenchmarkDotNet v0.14.0, macOS Sequoia 15.5 (24F74)
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.102
: .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 |
</code></pre>
<p>可见, ExSpan 的性能与 Span 很接近, 慢了 (121.74 / 96.30 - 1 =) 26% 左右.</p>
<h4 id="net-9">.NET 9</h4>
<pre><code>BenchmarkDotNet v0.14.0, macOS Sequoia 15.5 (24F74)
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 9.0.102
: .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 |
</code></pre>
<p>.NET 9 时性能又有进步, ExSpan 的性能与 Span 很接近了. 仅相差 (104.89 / 93.34 - 1 =) 12% 左右.</p>
<p>若想追求最佳性能, 也可利用指针进行优化. 可参考 SumForExSpanUsePtr 或 SumForExSpanUseRef, 它们都比 SumForSpan 快.</p>
<h2 id="文档">文档</h2>
<ul>
<li>Online document: https://zyl910.github.io/ExSpans_doc/</li>
<li>DocFX: Run <code>docfx_serve.bat</code>. Then browse http://localhost:8080/ .</li>
</ul>
</div>
<div id="MySignature" role="contentinfo">
<div id="MySignature"><div>作者:zyl910</div>
<div>出处:http://www.cnblogs.com/zyl910/</div>
<div>版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0.</div>
</div><br><br>
来源:https://www.cnblogs.com/zyl910/p/18930437/dotnet_dll_ExSpans_v1_0
頁:
[1]