咱們都知道,.Net Core是微軟推出的一個通用開發平臺,它是跨平臺和開源的,由一個.NET運行時、一組可重用的框架庫、一組SDK工具和語言編譯器組成,旨在讓.Net developers能夠更容易地編寫高性能的服務應用程序和基於雲的可伸縮服務,好比微服務、物聯網、雲原生等等;在這些場景下,對於內存的消耗每每十分敏感,也十分苛刻;爲了解決這個棘手問題,同時釋放應用開發人員的精力,讓他們可以安心地使用Net Core,而不用擔憂這些應用場景下的性能問題,故從.NET Core 2.1開始引進了兩個新的旗艦類型:Span<T>
、Memory<T>
,使用它們能夠避免分配緩衝區和沒必要要的數據複製。html
前面已經對span作了詳細地講解,因此今天主題是Memory,一樣以Why、What和How的方式緩緩道來 ,讓你知其然,更知其因此然。git
Memory<T>
是Span的補充,它是爲了解決Span沒法駐留到堆上而誕生的,能夠說Span是Memory的奠定,故在讀這篇文章前,請先仔細品讀前面兩篇文章:github
如今,做者就當你已經閱讀了前面的博客,並明白了Span的本質(ref-like type)和秉性特色(stack-only)。web
下面來看一個例子:編程
async Task DoSomethingAsync(Span<byte> buffer) {// 這裏編譯器會提示報錯,做爲例子而已,請忽略。 buffer[0] = 0; await Something(); // 異步方法會釋放當前執行棧,那麼Span也被回收了。 buffer[0] = 1; // 這裏buffer將沒法繼續。 }
備註:C#編譯器和core運行時內部會強制驗證Span的侷限性,因此上面例子纔會編譯不過。c#
正是由於這些侷限性,確保了更高效、安全的內存訪問。windows
也是由於這些侷限性,沒法用於須要將引用數據存儲到堆上的一些高級應用場景,好比:異步方法、類字段、泛型參數、集合成員、lambda表達式、迭代器等。api
仍是由於這些侷限性,增長了span對於高層開發人員的複雜性。數組
因此Memory<T>
誕生了,做爲span的補充,它就是目前的解決方案,沒有之一,也是高層開發人員往後使用最廣泛的類型。安全
和Span<T>
同樣,也是sliceable type
,但它不是ref-like type
,就是普通的C#結構體。這意味着,能夠將它裝箱到堆上、做爲類的字段或異步方法的參數、保存到集合等等,對於高層開發人員很是友好,嘿嘿,而且當須要處理Memory底層緩衝區,即作同步處理時,直接調用它的Span屬性,同時又得到了高效的索引能力。
備註:
Memory<T>
表示一段可讀寫的連續內存區域,ReadOnlyMemory
表示一段只讀的連續內存區域。
static async Task<uint> ChecksumReadAsync(Memory<byte> buffer, Stream stream) { var bytesRead = await stream.ReadAsync(buffer); // 須要同步處理時,直接調用span屬性。 return SafeSum(buffer.Span.Slice(0, bytesRead)); // 千萬不要這樣寫,除非你想要先持久化分片數據到託管堆上,但這又沒法使用Span<T>實現;其次Memory <T>是一個比Span<T>更大的結構體,切片每每相對較慢。 //return SafeSum(buffer.Slice(0,bytesRead).Span()); } static uint SafeSum(Span<byte> buffer) { uint sum = 0; foreach (var t in buffer) { sum += t; } return sum; }
public readonly struct Memory<T> { private readonly object _object; //表示Memory能包裹的對象,EveryThing。 private readonly int _index; private readonly int _length; public Span<T> Span { get; } // 實際的內部緩衝區 }
如前所述,Memory的目的是爲了解決Span沒法駐留到堆上的問題,也就是Memory表明的內存塊並不會隨方法執行棧的unwind
而回收,也就是說它的內部緩衝區是有生命週期的,並非短暫的,這就是爲何字段_object
的類型被設計成object
,而不是類型化爲T[],就是爲了經過傳遞IMemoryOwner
來管理Span的生命週期,從而避免UAF(use-after-free)bug。
private static MemoryPool<byte> _memPool = MemoryPool<byte>.Shared; public async Task UsageWithLifeAsync(int size) { using (var owner = _memPool.Rent(size)) // 從池裏租借一塊IMemoryOwner包裹的內存。 { await DoSomethingAsync(owner.Memory); // 把實際的內存借給異步方法使用。 } // 做用域結束,存儲的Memory<T>被回收,這裏是返還給內存池,有借有還,再借不難,嘿嘿。 } // 不用擔憂span會隨着方法執行棧unwind而回收 async Task DoSomethingAsync(Memory<byte> buffer) { buffer.Span[0] = 0; // 沒問題 await Something(); // 跨越await邊界。 buffer.Span[0] = 1; // 沒問題 }
IMemoryOwner
,顧名思義,Memory<T>
擁有者,經過屬性Memory來表示,以下:
public interface IMemoryOwner<T> : IDisposable { Memory<T> Memory { get; } }
因此,可使用IMemoryOwner
來轉移Memory<T>
內部緩衝區的全部權,從而讓開發人員沒必要管理緩衝區。
關於如何優雅地管理
Memory<T>
內部緩衝區的生命週期,在設計時工程師們考慮過好幾種方案,好比:聯合標識、自動引用計數(ARC)等,但最後仍是選擇上面這種方案,即經過實現IMemoryOwner
間接地從新得到Memory<T>
內部緩衝區的控制權,其實每次都是建立一個新的Span,因此能夠將Memory<T>
視爲Span<T>
的工廠。
如前所述, Memory<T>
其實就是Span<T>
的heap-able
類型,故它的API和span基本相同,以下:
public Memory(T[] array); public Memory(T[] array, int start, int length); public Memory<T> Slice(int start);// 支持sliceable public bool TryCopyTo(Memory<T> destination);
不一樣的是Memory<T>
有兩個獨一無二的API,以下:
public MemoryHandle Pin(); // 釘住_object的內存地址,即告知垃圾回收器不要回收它,咱們本身管理內存。 public System.Span<T> Span { get; }// 當_object字段爲數組時,提供快速索引的能力。
和Span<T>
同樣,一般Memory<T>
都是包裹數組、字符串,用法也基本相同,只是應用場景不同而已。
Memory<T>
的使用指南:
Memory<T>
做爲參數無返回值的同步方法,方法結束後,不該該再使用它。Memory<T>
做爲參數返回Task的異步方法,方法結束後,不該該再使用它。Memory<T>
實例不能同時被多個消費者使用。因此啊,千萬不要將好東西用錯地方了,聰明反被聰明誤,最後,弄巧成拙,嘿嘿。
綜上所述,和Span<T>
同樣,Memory<T>
也是Sliceable type
,它是Span沒法駐留到堆上的解決方案。通常Span<T>
由底層開發人員用在數據同步處理和轉換方面,而高層開發人員使用Memory<T>
比較多,由於它能夠用於一些高級的場景,好比:異步方法、類字段、lambda表達式、泛型參數等等。二者的完美運用就可以支持無複製流動數據,這就是數據管道應用場景(System.IO.Pipelines)。
到目前爲止,做者花了三篇博客終於把這兩個旗艦類型講完了,相信認真品讀這三篇博客的同窗,必定會受益不淺。後面的系列將講二者的高級應用場景,好比數據管道(Data Pipelines )、不連續緩衝區(Discontiguous Buffers)、緩衝池(Buffer Pooling)、以及爲何讓Aspnet Core Web Server變得如此高性能等。
一圖勝千言,最新一期techempower web框架基準測試:
若是有什麼疑問和看法,歡迎評論區交流。
若是你以爲本篇文章對您有幫助的話,感謝您的【推薦】。
若是你對.NET高性能編程感興趣的話能夠【關注我】,我會按期的在博客分享個人學習心得。
歡迎轉載,請在明顯位置給出出處及連接。
https://en.wikipedia.org/wiki/Reference_counting
https://msdn.microsoft.com/en-us/magazine/mt814808
https://blogs.msdn.microsoft.com/oldnewthing/20040406-00/?p=39903
https://github.com/dotnet/corefxlab/blob/master/docs/specs/memory.md
https://blogs.msdn.microsoft.com/dotnet/2018/05/30/announcing-net-core-2-1
https://docs.microsoft.com/zh-cn/dotnet/api/system.memory-1?view=netcore-2.2
https://frameworkbenchmarks.readthedocs.io/en/latest/Project-Information/Framework-Tests
https://blogs.msdn.microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-performance-io-in-net
https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T