一、Span<T>概述
原文:Provides a type- and memory-safe representation of a contiguous region of arbitrary memory.
中文的翻譯不准確,這里給出比較厚道的翻譯:提供類型T安全、連續的內存區域的表達方式.

這里出現高階語法 readonly ref struct,下面是msdn給的語言規范(或者其核心意義),估計大家會看暈,
Span<T> 並且不能跨 await 和 yield 邊界使用。 此外,對兩個方法的調用(Equals(Object) 和 GetHashCode)將引發一個 NotSupportedException。因為鎖定在堆棧上,所以也不要試圖讓其成為做為靜態成員。
我先給出最簡單的解釋:
Span<T>是微軟為了給.NET提供了一個高效的內存操縱元素,而定義的一個數據結構,為了高效的初衷,將Span<T>自身鎖定在堆棧上(內存連續,且處理高效)
注意:是Span<T>自身!!!Span<T>
實例通常用於保存數組或某個數組的一部分的元素。
二、Span<T>可用來做哪些事
2.1 不得不提的 Slice
切片這種東西,在GO,Rust中太尋常了(PS:當然對於C++的表示不屑),對於C#而言,這是一個性能提升不可或缺的概念,
Span基本上就是這個概念的翻版.所以其中有諸多方法就是切片.
可見微軟為Span提供了諸多類似於原來的String中的很多方法,具體查閱地址: Span的擴展方法
2.1 切片是其本質,是對原有對象的投影(或部分投影)
之前我們要實現高效的操作,如字符串類的操作,數組類的操作,
這里應該尤其注意,不見得你使用Span就高效了,明白它的設計初衷:Slice! 特別是會不斷產生新的碎片和構造新對象的場景.(由此可見對於String的操作產生了諸多
新碎片這樣的場景是尤其好用的)
我們看看如下的場景,大家覺得哪個效率會更高
int[] array = new int[10000]; Span<int> arraySpan = array; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int ctr = 0; ctr < arraySpan.Length; ctr++) arraySpan[ctr] = arraySpan[ctr] * arraySpan[ctr]/3; stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed); array = new int[10000]; stopwatch.Reset(); stopwatch.Start(); for (int ctr = 0; ctr < array.Length; ctr++) array[ctr] = array[ctr] * array[ctr]/3; Console.WriteLine(stopwatch.Elapsed);
結果按照我們的原則你就知道,不用Span效果會更好,下圖是realse模式下發布的,整體上可知:不用Span會更快,所以切片是它的本質!大家看看GO的切片就知道了.
3.1 Span<T>可以不僅投影常見對象還可以是從Marshal , stackalloc分配的而來的
var native = Marshal.AllocHGlobal(100); Span<byte> nativeSpan; unsafe { nativeSpan = new Span<byte>(native.ToPointer(), 100); } byte data = 0; for (int ctr = 0; ctr < nativeSpan.Length; ctr++) nativeSpan[ctr] = data++; int nativeSum = 0; foreach (var value in nativeSpan) nativeSum += value; Console.WriteLine($"The sum is {nativeSum}"); Marshal.FreeHGlobal(native);
byte data = 0; Span<byte> stackSpan = stackalloc byte[100]; for (int ctr = 0; ctr < stackSpan.Length; ctr++) stackSpan[ctr] = data++; int stackSum = 0; foreach (var value in stackSpan) stackSum += value; Console.WriteLine($"The sum is {stackSum}");
三、Memory<T>概述
和Span<T>類似,它同樣表示連續內存區域。區別是沒有Span<T>堆棧上的限制,沒有 readonly ref struct 這樣的申明了.
這意味着 Memory<T> 可以放置在托管堆上,而 Span<T> 不能。 因此,Memory<T> 結構與 Span<T> 實例沒有相同的限制。
具體而言:它可用作類中的字段。它可跨 await 和 yield 邊界使用。除了 Memory<T>之外,還可以使用 System.ReadOnlyMemory<T> 來表示不可變或只讀內存。
這里有園友從C++源碼的角度進行分析,這里提取下面兩段,供大家參閱(鏈接地址),
Span 與 Memory 的區別:
1.Memory<T> 保存 原有的對象地址、子內容的開始地址 與 子內容的長度,大致情況下圖:
如上文所說,Span被微軟鎖定在堆棧上,
2.Span 保存子內容的開始地址與長度,不保存原始對象的地址,大致如下圖:
如果這就是真實情況,可見Span脫離不了堆棧的環境的,不然計算不了真實的切片地址的.
三、使用上的預測和建議
1.類似於string這樣的操作會Span大有用處(因為會產生很多新的中間數據產生開銷)
2.無論span還是memory設計初衷就是Slice,使用場景是那些會不斷產生新的開銷、新的對象的場景.
3.其實所有的性能提升根本讓CPU運行的指令更少了,減少了不必要的開銷