C# 7.2 中的 Span<T> 和 Memory<T> 有啥区别?
Posted
技术标签:
【中文标题】C# 7.2 中的 Span<T> 和 Memory<T> 有啥区别?【英文标题】:What is the difference between Span<T> and Memory<T> in C# 7.2?C# 7.2 中的 Span<T> 和 Memory<T> 有什么区别? 【发布时间】:2022-01-14 02:50:29 【问题描述】:C# 7.2 引入了两种新类型:Span<T>
和 Memory<T>
,它们比早期的 C# 类型(如 string[]
)具有更好的性能。
问题:Span<T>
和Memory<T>
有什么区别?为什么我要使用一个而不是另一个?
【问题讨论】:
另见:C# 7.2: Understanding Span @JeffMercado 除了 string[] 之外,还有哪些 C# 类型可以替换的完整列表吗?它们仅用于数组还是也可以用于代替 ListSpan<T>
本质上是仅堆栈,而Memory<T>
可以存在于堆中。
Span<T>
是我们正在添加到平台以表示的新类型 任意内存的连续区域,具有性能 特性与 T[] 相当。它的API类似于数组, 但与数组不同,它可以指向托管内存或本机内存,或者 分配到堆栈上的内存。
Memory <T>
是对Span<T>
的补充类型。正如其设计中所讨论的 文档,Span<T>
是仅堆栈类型。仅堆栈的性质Span<T>
使其不适用于很多需要存储的场景 对堆上的缓冲区(用Span<T>
表示)的引用,例如为了 执行异步调用的例程。
async Task DoSomethingAsync(Span<byte> buffer)
buffer[0] = 0;
await Something(); // Oops! The stack unwinds here, but the buffer below
// cannot survive the continuation.
buffer[0] = 1;
为了解决这个问题,我们将提供一组互补的类型, 旨在用作表示的通用交换类型, 就像
Span <T>
,一个任意内存范围,但不像Span <T>
这些类型不会是仅堆栈的,代价是显着的 读取和写入内存的性能损失。
async Task DoSomethingAsync(Memory<byte> buffer)
buffer.Span[0] = 0;
await Something(); // The stack unwinds here, but it's OK as Memory<T> is
// just like any other type.
buffer.Span[0] = 1;
在上面的示例中,
Memory <byte>
用于表示缓冲区。 它是常规类型,可用于执行异步的方法 来电。它的 Span 属性返回Span<byte>
,但返回值 在异步调用期间不会存储在堆上,而是 从Memory<T>
值生成新值。从某种意义上说,Memory<T>
是Span<T>
的工厂。
参考文档:here
【讨论】:
“仅堆栈性质”是什么意思? 这里是菜鸟。堆栈不会回滚到等待之前的位置吗?我不明白为什么缓冲区无法在Span<byte> buffer
示例中继续存在。为什么我们将地址松散到缓冲区 [0] 指向的内存点?
@Spectraljump 如果我没记错的话,异步方法中的任何变量实际上在编译后都会成为类字段(因此,在堆上),以便能够使用那些等待后的值。如果 Span 只能在堆栈上,那么在 await 之前和之后它就不会是相同的 Span。
@Spectraljump Async 方法将被转换为状态机。因此状态机的第一部分(在等待之前)被执行,它将退出该方法。当等待的任务完成后,将执行下一个状态(单独的方法),这就是第一个状态的堆栈展开的原因。
如果我们打算使用,为什么文档会说“以显着的性能损失为代价读取和写入内存” Memory<T>
在很多情况下我们不能使用Span<T>
/ReadOnlySpan<T>
?【参考方案2】:
re: 这意味着它只能指向分配在堆栈上的内存。
Span<T>
可以指向任何内存:分配在堆栈或堆上。 Span<T>
的仅堆栈性质意味着 Span<T>
本身(而不是它指向的内存)必须仅驻留在堆栈上。这与“普通”C# 结构形成对比,后者可以驻留在堆栈或堆上(通过值类型装箱,或者当它们嵌入到类/引用类型中时)。一些更明显的实际含义是,你不能在一个类中拥有 Span<T>
字段,你不能框 Span<T>
,你不能将它们组成一个数组。
【讨论】:
啊,另一种类似 byref 的类型。 但是为什么只能驻留在 Stack 上呢?为什么你不能把它们装箱?还是不能让他们上课? 如果您可以在堆上的任何位置获得 SpanMemory
(至少不能直接)你可以试试Memory<byte> mem = stackalloc byte[100];
并得到编译错误跨度>
【参考方案3】:
Memory<T>
可以被视为Span<T>
的不安全但更通用的版本。如果Memory<T>
对象指向已释放的数组,则访问该对象将失败。
【讨论】:
以上是关于C# 7.2 中的 Span<T> 和 Memory<T> 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
C# 7.1 和 7.2 Span 和 ReadOnlySpan
如何将 C# 字符串转换为 Span<char>? (跨度<T>)
在 C# 中使用 Span<T; 和 Memory<T; 编写高性能代码