系列介紹
【五分鐘的dotnet】是一個利用您的碎片化時間來學習和豐富.net知識的博文系列,它所包含了.net體系中可能會涉及到的方方面面,比如C#的小細節,AspnetCore,微服務中的.net知識等等,
5min+不是超過5分鐘的意思,"+"是知識的增加,so,它是讓您花費5分鐘以下的時間來提升您的知識儲備量,
正文
在上一篇文章:《閃電光速拳? .NetCore 中的Span》 中我們提到了在.net core 2.x 所新增的一個型別:Span,
它與咱們傳統使用的基礎型別相比具有超高的性能,原因是它減少了大量的記憶體分配和資料量復制,并且它所分配的資料記憶體是連續的,
但是您會發現它無法用在我們專案的某些地方,它獨特的 ref結構 使它沒有辦法跨執行緒使用、更沒有辦法使用Lambda運算式,

特別是在AspNetCore中,咱們會使用到大量的異步操作方法,“所以,這個時候如果我們又想跨執行緒操作資料又想獲得類似Span這樣的性能怎么辦呢?” 上一篇文章我們留下了這樣的一個問題,所以現在就是到了還愿的時候了,它就是與Span一起發布的孿生兄弟: Memory,

獅子座和射手座黃金圣斗士同樣具備超越光速的能力
什么是Memory
那什么是Memory呢?不妨我們先來猜測一下,它的結構是什么樣子,畢竟它是Span的孿生兄弟,而Span的結構我們在前面就了解過了:
public readonly ref struct Span<T>
{
public void Clear();
public void CopyTo([NullableAttribute(new[] { 0, 1 })] Span<T> destination);
public void Fill(T value);
public Enumerator GetEnumerator();
public Span<T> Slice(int start, int length);
public T[] ToArray();
public override string ToString();
//.....
}
當時我們說Span有各種缺陷的原因是由于它獨特的 ref struct 關鍵字所導致的,導致它無法拆箱裝箱、無法書寫Lambda、無法跨執行緒等,但是它兄弟卻可以克服缺點,所以我們想想它會和Span在宣告上有哪些差距呢? 是的,您可能已經想到了:它不會有 ref 關鍵字了,
所以,我們看到它的內部結構就是醬紫的:
public readonly struct Memory<T>
{
public static Memory<T> Empty { get; }
public bool IsEmpty { get; }
public int Length { get; }
public Span<T> Span { get; }
public void CopyTo([NullableAttribute(new[] { 0, 1 })] Memory<T> destination);
public MemoryHandle Pin();
public Memory<T> Slice(int start, int length);
public T[] ToArray();
public override string ToString();
}
和我們猜想的一樣,它少了ref關鍵字,內部方法也和Span差不多(同樣擁有CopyTo,Slice等),但是還是有一些差異,比如多了Pin方法,Span屬性等,
被宣告為ref struct的結構,叫做“ByRefLike”,所以在我們在進行反射的時候,我們使用Type會看到有這樣一個屬性:IsByRefLike,

好像有點超綱了哈(>人<;)
按照MSDN給出的解釋:
該結構是使用中的C# ref struct 關鍵字宣告的, 不能將類似 byref 的結構的實體放置在托管堆上,
所以這也是為什么上一篇文章說的:Span只能放置在記憶體堆疊中的原因,
那么反過來想,沒有了ref關鍵字之后,Memory是不是就可以放置在托管堆上了呢?是不是就可以進行拆裝箱,克隆副本供其它執行緒的記憶體堆疊使用了呢? 好吧,可能是這樣,所以這也許就是它能夠被允許跨執行緒使用的原因吧,
進行到了這一步,那我們再回過頭來想想Memory是什么呢? 其實作在我們心里其實都已經有個底了:
與 Span<T>一樣,Memory<T> 表示記憶體的連續區域, 但 Span<T>不同,Memory<T> 不是ref 結構, 這意味著 Memory<T> 可以放置在托管堆上,而 Span<T> 不能, 因此,Memory<T> 結構與 Span<T> 實體沒有相同的限制, 具體而言:
- 它可用作類中的欄位,
- 它可跨 await 和 yield 邊界使用,
除了 Memory<T>之外,還可以使用 System.ReadOnlyMemory<T> 來表示不可變或只讀記憶體,
這是MSDN給出來的解釋,不是我亂編的哈??!(雖然和我們上面猜的一模一樣(●ˇ?ˇ●))
接下來,我們來看看他們到底有多像:

好吧,為了做該圖我已經使用了美工必殺器 - ps??
有沒有發現,除了名字之外,好像其它的都一模一樣??,甚至直接連注釋都懶得改了,
一樣卻又不一樣
既然作為孿生兄弟,必然有一些共通之處,而Memory作為對Span的增強(應該也算不算增強吧),那么內部的實作可能很多會與Span相似,
是的,查看Memory的源代碼您就會發現,它的內部某些方法就是通過Span來實作的:
public readonly struct Memory<T>
{
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
public T[] ToArray() => Span.ToArray();
}
有關Memory的源代碼,您可以點此查看:the source code of Memory,
所以您會發現Memory是可以直接轉換為Span的,但是Memory作為一個可以跨執行緒的型別被轉換為Span是相對危險的,所以Dotnet Core的開發人員直接在備注上寫了這樣的文字:
Such a cast can only be done with unsafe or marshaling code,in which case that's the dangerous operation performed by the dev, and we're just following suit here to make it work as best as possible.
意思就是這種轉換很危險,我來幫你做了算了,

如何使用
來吧,修改上面的Span會在Task中報錯的例子:
public async Task MemoryCanInLambda(Memory<string> buffer)
{
await Task.Factory.StartNew(() =>
{
buffer.Trim("s");
});
}
此時我們就可以在異步中使用Memory了,采用連續記憶體+指標級別的操作方案來操作資料內容,豈不爽歪歪?
異步的資料交由Memory,同步的資料交由Span,ForExample:
static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
{
int bytesRead = await stream.ReadAsync(buffer);
return Checksum(buffer.Span.Slice(0, bytesRead));
// Or buffer.Slice(0, bytesRead).Span
}
static int Checksum(Span<byte> buffer) { ... }
正是由于Span和Memory帶來的巨大性能優化,所以.NET Core的開發者們做了一件非常瘋狂的事:為.NET的庫添加了數百個多載方法, 比如,您現在可以看到我們經常使用的Int.Parse方法居然支持了Span,它的簽名是醬紫:
public static Int32 Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, [NullableAttribute(2)] IFormatProvider? provider = null);
除此之外,還有long,double…………甚至連Guid和DateTime都有這樣的多載,
還有其它常用的各種類也開始支持以Span作為引數的多載方法了,比如Random、StringBuilder等,
public StringBuilder Append(ReadOnlySpan<char> value);
先不談重建這些基礎常用型別的多載作業量有多大,我們應該想想.NET為什么要這么做呢?就是為了我們能夠使用Span和Memory來代替我們現有的一些操作,從而提升性能,
那么僅僅是開發底層框架才適合用它們嗎? 當然不是,就好比是截取字串的操作,無論是底層框架還是應用程式級別的代碼都會用到,所以如果有可能,而當我們的專案又正好是.netCore 2.x以上的版本,為何不去嘗試使用下呢?
不要因為“我知道Span不過就是把原有的某某操作放到記憶體某處,不過如此”,就對它產生偏見,確實,Span的實作很簡單,您如果有興趣可以查看它的實作代碼,.net core正在為它的實作和使用做巨大的適配作業,C# 從7.x 開始就不斷對異步操作和記憶體分配進行優化,這或許也為我們未來.NET的發展給了一點點提示,加油,偉大的開發人員們,(? ?_?)?
最后,小聲說一句:創作不易,點個推薦吧??

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/69017.html
標籤:.NET Core
