1. 程式人生 > >.net下的span和memory

.net下的span和memory

讀取 microsoft cast red 無法 隱式轉換 取數 泛型 相對

.net core 2.1的重頭戲就是性能,其中最重要的兩個類就是span和memory,本文這裏簡單的介紹一下這兩個類的使用。

什麽是 Span<T>

Span<T> 是新一種新值類型。它表示一段連續的區域,它通常和數組關聯,表示數組中的一部分內存。

var????????arr???=?new?byte[10];
Span<byte>?bytes?=?arr;

也可以取數組中的一部分:

var?bytes?=?new?Span<byte>(arr,?3,?5);

初一乍看,span<T>和ArraySegment<T>非常類似,但span更加強大得多,它不但能用於分離數組,還可以引用棧上的數據。

Span<byte>?bytes?=?stackalloc?byte[2];

也可以引用指針數據,

Span<byte>?bytes;
unsafe?{?bytes?=?new?Span<byte>((byte*)ptr,?1);?}

另外,span還支持 reinterpret_cast 的理念,即可以將 Span<byte> 強制轉換為 Span<int>,配合MemoryMarshal類使用,span<T>大多數的時候都可以代替指針了。

除了功能更加強大外,span在bcl庫中也得到了更多的支持,大多數支持數組的函數現在基本上都能直接支持span了,如:

var?inputSpan?=?input.AsSpan();
int?first?????=?int.Parse(inputSpan.Slice(3,?5));

這個函數中,int.Parse函數就能直接支持span,並且由於不產生子字符串,比使用substring的方法性能更高。

另外,系統也支持數組類型到span的隱式轉換,同時提供了AsSpan的顯示擴展方法,方便將數組類型轉換為span。

除了功能強大外,span的性能也是非常高的,對span的操作基本上和訪問數組一樣高,無需通過計算來確定指針開頭及其起始偏移,因為"引用"字段本身已對兩者進行了封裝。相比之下,ArraySegment<T> 有單獨的偏移字段,這就增加了索引編制和數據傳遞操作的成本。

什麽是 Memory<T>

Span<T>雖然強大而好用,但它只能存在於棧上,而不能存在於堆上,原因主要有如下兩點:

  1. span包含"引用"字段(如數組的開頭),這些引用被稱為"內部指針"。對於 .NET 運行時的垃圾回收器,跟蹤這些指針是一項成本相對高昂的操作。因此,運行時將這些引用約束為僅存在於堆棧上,因為它隱式規定了可以存在的內部指針數量下限。
  2. 對 Span 執行的讀取和寫入操作不是原子操作。如果多個線程同時對 Span 在堆上的字段執行讀取和寫入操作,存在"撕裂"風險。

這個限制決定了無法將 Span 裝箱,進而無法將 Span<T> 與現有反射調用 API結合使用,也無法作為泛型參數。

對於大部分同步處理功能,這個並沒有太大的影響,但由於span<T>無法存儲到堆,從而導致其無法在異步上下文中使用。為了解決這個問題,.net引入了一個新類型Memory<T>。

Memory和span的使用方法大同小異,

var?arr???=?new?byte[10];
var?bytes?=?new?Memory<byte>(arr,?3,?5);

不同之處在於 Memory<T> 是不類似引用的結構,可以存在於堆上。.net bcl庫對memory也做了很好的支持,如Stream.ReadAsync就能直接支持memory<byte>作為參數。

另外,也可以從Memory的Span屬性創建指向該Memory的span,這樣也可以使用span的強大的功能。

參考文章:

C# - Span 全面介紹:探索 .NET 新增的重要組成部分

.net下的span和memory