《ASP.NET Core 高效能系列》Span<T>和Memory<T>
一、Span<T>概述
原文:Provides a type- and memory-safe representation of a contiguous region of arbitrary memory.
中文的翻譯不準確,這裡給出比較厚道的翻譯:提供型別T安全、連續的記憶體區域的表達方式.
(圖1:Span<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執行的指令更少了,減少了不必要的開銷