为什么Span比内存快10倍

maz*_*301 2 c# optimization performance stack

我有字节数组

private byte[] _data;


[Benchmark()]
public void SpanTest()
{
    var temp = _data.AsSpan();
    var length = BitConverter.ToInt32(temp.Slice(0, 4));
    var buffData = temp.Slice(4, length);
    var s = buffData[0];
}

[Benchmark()]
public async Task MemoryTest()
{
    var temp = _data.AsMemory();
    var length = BitConverter.ToInt32(temp.Slice(0, 4).Span);
    var buffData = temp.Slice(4, length);
    var s = buffData.Span[0];
}
Run Code Online (Sandbox Code Playgroud)

基准测试结果

我不明白为什么 Span 比内存快 10 倍。从我的角度来看,Span 是在堆栈上分配的,但它指向堆上的数据(在我的例子中)。内存是在堆上分配的,这只是我在这里看到的一个区别。我的问题是为什么 Span 这么快?我读过有关 ref structs 的内容,但不明白它是如何工作的。

小智 6

您观察到的 Span 和 Memory 之间的性能差异主要是由于 Span 是仅堆栈数据结构,而 Memory 可以在堆上使用。这种差异以多种方式影响性能。

  1. 堆栈分配:由于 Span 是仅堆栈的数据结构,因此它受益于堆栈提供的快速分配和释放。堆栈分配比堆分配更快,因为它只是调整堆栈指针的问题,而堆分配需要搜索适当大小的内存块。
  2. 无垃圾回收:堆栈分配的对象(如 Span)在超出范围时会自动释放,无需任何垃圾回收。另一方面,内存对象会受到垃圾收集的影响,这会带来开销并会影响性能。
  3. Ref struct:正如您所提到的,Span 是一个 ref struct。Ref 结构不能存储在堆上,只能通过引用传递。这意味着它们不能被装箱或被闭包捕获,从而降低了意外堆分配的风险和相关的性能开销。

但是,需要注意的是,Span 和 Memory 之间的性能差异并不总是像基准测试结果所示的那样显着。相对性能可能会根据具体用例和正在处理的数据的性质而有所不同。

在您的特定示例中,性能差异可能与跨度与内存无关,而更多地与您使用异步方法(异步任务)进行 MemoryTest 基准测试这一事实有关。异步方法会由于状态机而引入开销,并且这种开销可能会影响基准测试结果。如果删除异步任务并使用常规同步方法进行 MemoryTest 基准测试,SpanTest 和 MemoryTest 之间的性能差异可能会变得不那么明显。