日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C#如何安全、高效地玩转任何种类的内存之Memory(三)

發布時間:2023/12/4 C# 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#如何安全、高效地玩转任何种类的内存之Memory(三) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

我們都知道,.Net Core是微軟推出的一個通用開發平臺,它是跨平臺和開源的,由一個.NET運行時、一組可重用的框架庫、一組SDK工具和語言編譯器組成,旨在讓.Net developers可以更容易地編寫高性能的服務應用程序和基于云的可伸縮服務,比如微服務、物聯網、云原生等等;在這些場景下,對于內存的消耗往往十分敏感,也十分苛刻;為了解決這個棘手問題,同時釋放應用開發人員的精力,讓他們能夠安心地使用Net Core,而不用擔心這些應用場景下的性能問題,故從.NET Core 2.1開始引進了兩個新的旗艦類型:Span<T>?、Memory<T>?,使用它們可以避免分配緩沖區和不必要的數據復制

前面已經對span做了詳細地講解,所以今天主題是Memory,同樣以Why、What和How的方式緩緩道來 ,讓你知其然,更知其所以然。

Memory<T>是Span的補充,它是為了解決Span無法駐留到堆上而誕生的,可以說Span是Memory的奠基,故在讀這篇文章前,請先仔細品讀前面兩篇文章:

  • 通俗易懂,C#如何安全、高效地玩轉任何種類的內存之Span的本質(一)。

  • 通俗易懂,C#如何安全、高效地玩轉任何種類的內存之Span的脾氣秉性(二)。

現在,作者就當你已經閱讀了前面的博客,并明白了Span的本質(ref-like type)和秉性特點(stack-only)。

why - 為什么需要memory ?

span的局限性

  • span只能存儲到執行棧上,保障操作效率與數組一樣高,并提供穩定的生命周期。

  • span不能被裝箱到堆上,避免棧撕裂問題。

  • span不能用作泛型類型參數

  • Span不能作為類的字段

  • Span不能實現任何接口

  • Span不能用于異步方法,因為無法跨越await邊界,所有無法跨異步操作暫留。

  • 下面來看一個例子:

    async Task DoSomethingAsync(Span<byte> buffer) {// 這里編譯器會提示報錯,作為例子而已,請忽略。buffer[0] = 0; ? ?await Something(); // 異步方法會釋放當前執行棧,那么Span也被回收了。buffer[0] = 1; // 這里buffer將無法繼續。}

    備注:C#編譯器和core運行時內部會強制驗證Span的局限性,所以上面例子才會編譯不過。

    正是因為這些局限性,確保了更高效、安全的內存訪問

    也是因為這些局限性,無法用于需要將引用數據存儲到堆上的一些高級應用場景,比如:異步方法、類字段、泛型參數、集合成員、lambda表達式、迭代器等

    還是因為這些局限性,增加了span對于高層開發人員的復雜性

    所以Memory<T>誕生了,作為span的補充,它就是目前的解決方案,沒有之一,也是高層開發人員日后使用最普遍的類型。

    what - memory是什么 ?

    和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; }

    Memory核心設計

    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>的工廠。

    How - 如何運用memory ?

    如前所述,?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);// 支持sliceablepublic 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>的使用指南

    • 同步方法應該接受Span

      參數,異步方法應該接受Memory參數。
    • 以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框架基準測試:

    最后

    如果有什么疑問和見解,歡迎評論區交流。

    延伸閱讀

    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

    https://docs.microsoft.com/en-us/previous-versions/windows/silverlight/dotnet-windows-silverlight/khk3k17t(v=vs.95)

    https://blogs.msdn.microsoft.com/mazhou/2018/03/25/c-7-series-part-10-spant-and-universal-memory-management

    相關文章:

    • .Net Core中使用ref和Span<T>提高程序性能

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

    • 有關C# 8.0、.NET Framework 4.8與NET Standard 2.1的一個說明

    • 通俗易懂,C#如何安全、高效地玩轉任何種類的內存之Span

    • 通俗易懂,C#如何安全、高效地玩轉任何種類的內存之Span的脾氣秉性(二)

    原文地址:https://www.cnblogs.com/justmine/p/10092344.html

    .NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com


    總結

    以上是生活随笔為你收集整理的C#如何安全、高效地玩转任何种类的内存之Memory(三)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。