[英]What is the difference between Span<T> and Memory<T> in C# 7.2?
C# 7.2 引入了兩種新類型: Span<T>
和Memory<T>
,它們比早期的 C# 類型(如string[]
具有更好的性能。
問題: Span<T>
和Memory<T>
有什么區別? 為什么我要用一個而不是另一個?
Span<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>
的工廠。
參考文件: 這里
re:這意味着它只能指向堆棧上分配的內存。
Span<T>
可以指向任何內存:在堆棧或堆上分配。 Span<T>
的僅堆棧特性意味着Span<T>
本身(不是它指向的內存)必須僅駐留在堆棧上。 這與“普通”C#結構形成對比,后者可以駐留在堆棧上或堆上(通過值類型裝箱,或者當它們嵌入在類/引用類型中時)。 一些更明顯的實際意義是你不能在類中有一個Span<T>
字段,你不能將Span<T>
包裝起來,並且你不能創建它們的數組。
引用結構不能存儲在堆上,編譯器會阻止您這樣做,因此不允許以下內容:
stackalloc不能與Memory一起使用(因為不能保證它不會存儲在堆上)但可以與Span一起使用
// this is legit Span<byte> data = stackalloc byte[256]; // legit // compile time error: Conversion of a stackalloc expression of type 'byte' to type 'Memory<byte>' is not possible. Memory<byte> data = stackalloc byte[256];
這意味着在某些情況下, Span本身無法進行各種微優化,因此應該使用Memory代替。
這是一個字符串分配自由Split方法的示例,它適用於ReadOnlyMemory結構,在Span上實現它真的很困難,因為Span是一個引用結構並且不能放入數組或IEnumerable中:
(實施摘自C# in a nutshell book )
IEnumerable<ReadOnlyMemory<char>> Split(ReadOnlyMemory<char> input)
{
int wordStart = 0;
for (int i = 0; i <= input.Length; i++)
{
if (i == input.Length || char.IsWhiteSpace(input.Span[i]))
{
yield return input.Slice(wordStart, i);
wordStart = i + 1;
}
}
}
這是通過do.net 基准庫針對常規Split方法在 .NET SDK=6.0.403 上進行的非常簡單的基准測試的結果。
| Method | StringUnderTest | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|---------------------- |-------------------- |------------------:|------------------:|------------------:|----------:|----------:|---------:|-----------:|
| RegularSplit | meow | 13.194 ns | 0.2891 ns | 0.3656 ns | 0.0051 | - | - | 32 B |
| SplitOnReadOnlyMemory | meow | 8.991 ns | 0.1981 ns | 0.2433 ns | 0.0127 | - | - | 80 B |
| RegularSplit | meow(...)meow [499] | 1,077.807 ns | 21.2291 ns | 34.8801 ns | 0.6409 | 0.0095 | - | 4024 B |
| SplitOnReadOnlyMemory | meow(...)meow [499] | 9.036 ns | 0.2055 ns | 0.2366 ns | 0.0127 | - | - | 80 B |
| RegularSplit | meo(...)eow [49999] | 121,740.719 ns | 2,221.3079 ns | 2,077.8128 ns | 63.4766 | 18.5547 | - | 400024 B |
| SplitOnReadOnlyMemory | meo(...)eow [49999] | 9.048 ns | 0.2033 ns | 0.2782 ns | 0.0127 | - | - | 80 B |
| RegularSplit | me(...)ow [4999999] | 67,502,918.403 ns | 1,252,689.2949 ns | 2,092,962.4006 ns | 5625.0000 | 2375.0000 | 750.0000 | 40000642 B |
| SplitOnReadOnlyMemory | me(...)ow [4999999] | 9.160 ns | 0.2057 ns | 0.2286 ns | 0.0127 | - | - | 80 B |
這些方法的輸入是“meow”字符串重復 1、100、10_000 和 1_000_000 次,我的基准設置並不理想,但它顯示了差異。
Memory<T>
可以看作是一個不安全但更通用的Span<T>
版本。 如果指向已釋放的數組,則對Memory<T>
對象的訪問將失敗。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.