<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      .NET高性能編程 - C#如何安全、高效地玩轉(zhuǎn)任何種類的內(nèi)存之Span的本質(zhì)(一)。

      前言

      作為.net程序員,使用過指針,寫過不安全代碼嗎?

      為什么要使用指針,什么時候需要使用它,以及如何安全、高效地使用它?

      如果能很好地回答這幾個問題,那么就能很好地理解今天了主題了。C#構(gòu)建了一個托管世界,在這個世界里,只要不寫不安全代碼,不操作指針,那么就能獲得.Net至關(guān)重要的安全保障,即什么都不用擔(dān)心;那如果我們需要操作的數(shù)據(jù)不在托管內(nèi)存中,而是來自于非托管內(nèi)存,比如位于本機內(nèi)存或者堆棧上,該如何編寫代碼支持來自任意區(qū)域的內(nèi)存呢?這個時候就需要寫不安全代碼,使用指針了;而如何安全、高效地操作任何類型的內(nèi)存,一直都是C#的痛點,今天我們就來談?wù)勥@個話題,講清楚 What、How 和 Why ,讓你知其然,更知其所以然,以后有人問你這個問題,就讓他看這篇文章吧,呵呵。

      what - 痛點是什么?

      回答這個問題前,先總結(jié)一下如何用C#操作任何類型的內(nèi)存:

      1. 托管內(nèi)存(managed memory )

        var mangedMemory = new Student();
        

        很熟悉吧,只需使用new操作符就分配了一塊托管內(nèi)存,而且還不用手工釋放它,因為它是由垃圾收集器(GC)管理的,GC會智能地決定何時釋放它,這就是所謂的托管內(nèi)存。默認(rèn)情況下,GC通過復(fù)制內(nèi)存的方式分代管理小對象(size < 85000 bytes),而專門為大對象(size >= 85000 bytes)開辟大對象堆(LOH),管理大對象時,并不會復(fù)制它,而是將其放入一個列表,提供較慢的分配和釋放,而且很容易產(chǎn)生內(nèi)存碎片。

      2. 棧內(nèi)存(stack memory )

        unsafe{
            var stackMemory = stackalloc byte[100];
        }
        

        很簡單,使用stackalloc關(guān)鍵字非常快速地就分配好了一塊內(nèi)存,也不用手工釋放,它會隨著當(dāng)前作用域而釋放,比如方法執(zhí)行結(jié)束時,就自動釋放了。棧內(nèi)存的容量非常小( ARM、x86 和 x64 計算機,默認(rèn)堆棧大小為 1 MB),當(dāng)你使用棧內(nèi)存的容量大于1M時,就會報StackOverflowException 異常 ,這通常是致命的,不能被處理,而且會立即干掉整個應(yīng)用程序,所以棧內(nèi)存一般用于需要小內(nèi)存,但是又不得不快速執(zhí)行的大量短操作,比如微軟使用棧內(nèi)存來快速地記錄ETW事件日志。

      3. 本機內(nèi)存(native memory )

        IntPtr nativeMemory0 = default(IntPtr), nativeMemory1 = default(IntPtr);
        try
        {
            unsafe
            {
                nativeMemory0 = Marshal.AllocHGlobal(256);
                nativeMemory1 = Marshal.AllocCoTaskMem(256);
            }
        }
        finally
        {
            Marshal.FreeHGlobal(nativeMemory0);
            Marshal.FreeCoTaskMem(nativeMemory1);
        }
        

        通過調(diào)用方法Marshal.AllocHGlobal Marshal.AllocCoTaskMem 來分配非托管內(nèi)存,非托管就是垃圾回收器(GC)不可見的意思,并且還需要手工調(diào)用方法Marshal.FreeHGlobal or Marshal.FreeCoTaskMem 釋放它,千萬不能忘記,不然就內(nèi)存泄漏了。

      拋磚引玉 - 痛點

      首先我們設(shè)計一個解析完整或部分字符串為整數(shù)的API,如下

      public interface IntParser
      {
          // allows us to parse the whole string.
          int Parse(string managedMemory);
      
          // allows us to parse part of the string.
          int Parse(string managedMemory, int startIndex, int length);
      
          // allows us to parse characters stored on the unmanaged heap / stack.
          unsafe int Parse(char* pointerToUnmanagedMemory, int length);
      
          // allows us to parse part of the characters stored on the unmanaged heap / stack.
          unsafe int Parse(char* pointerToUnmanagedMemory, int startIndex, int length); 
      }
      

      從上面可以看到,為了支持解析來自任何內(nèi)存區(qū)域的字符串,一共寫了4個重載方法。

      接下來在來設(shè)計一個支持復(fù)制任何內(nèi)存塊的API,如下

      public interface MemoryblockCopier
      {
          void Copy<T>(T[] source, T[] destination);
          void Copy<T>(T[] source, int sourceStartIndex, T[] destination, int destinationStartIndex, int elementsCount);
          unsafe void Copy<T>(void* source, void* destination, int elementsCount);
          unsafe void Copy<T>(void* source, int sourceStartIndex, void* destination, int destinationStartIndex, int elementsCount);
          unsafe void Copy<T>(void* source, int sourceLength, T[] destination);
          unsafe void Copy<T>(void* source, int sourceStartIndex, T[] destination, int destinationStartIndex, int elementsCount);
      }
      

      腦袋蒙圈沒,以前C#操縱各種內(nèi)存就是這么復(fù)雜、麻煩。通過上面的總結(jié)如何用C#操作任何類型的內(nèi)存,相信大多數(shù)同學(xué)都能夠很好地理解這兩個類的設(shè)計,但我心里是沒底的,因為使用了不安全代碼和指針,這些操作是危險的、不可控的,根本無法獲得.net至關(guān)重要的安全保障,并且可能還會有難以預(yù)估的問題,比如堆棧溢出、內(nèi)存碎片、棧撕裂等等,微軟的工程師們早就意識到了這個痛點,所以span誕生了,它就是這個痛點的解決方案

      how - span如何解決這個痛點?

      先來看看,如何使用span操作各種類型的內(nèi)存(偽代碼):

      1. 托管內(nèi)存(managed memory )

        var managedMemory = new byte[100];
        Span<byte> span = managedMemory;
        
      2. 棧內(nèi)存(stack memory )

        var stackedMemory = stackalloc byte[100];
        var span = new Span<byte>(stackedMemory, 100);
        
      3. 本機內(nèi)存(native memory )

        var nativeMemory = Marshal.AllocHGlobal(100);
        var nativeSpan = new Span<byte>(nativeMemory.ToPointer(), 100);
        

      span就像黑洞一樣,能夠吸收來自于內(nèi)存任意區(qū)域的數(shù)據(jù),實際上,現(xiàn)在,在.Net的世界里,Span就是所有類型內(nèi)存的抽象化身,表示一段連續(xù)的內(nèi)存,它的API設(shè)計和性能就像數(shù)組一樣,所以我們完全可以像使用數(shù)組一樣地操作各種內(nèi)存,真的是太方便了。

      現(xiàn)在重構(gòu)上面的兩個設(shè)計,如下:

      public interface IntParser
      {
          int Parse(Span<char> managedMemory);
          int Parse(Span<char>, int startIndex, int length);
      }
      public interface MemoryblockCopier
      {
          void Copy<T>(Span<T> source, Span<T> destination); 
          void Copy<T>(Span<T> source, int sourceStartIndex, Span<T> destination, int destinationStartIndex, int elementsCount);
      }
      

      上面的方法根本不關(guān)心它操作的是哪種類型的內(nèi)存,我們可以自由地從托管內(nèi)存切換到本機代碼,再切換到堆棧上,真正的享受玩轉(zhuǎn)內(nèi)存的樂趣。

      why - 為什么span能解決這個痛點?

      淺析span的工作機制

      先來窺視一下源碼:

      我已經(jīng)圈出的三個字段:偏移量、索引、長度(使用過ArraySegment<byte> 的同學(xué)可能已經(jīng)大致理解到設(shè)計的精髓了),這就是它的主要設(shè)計,當(dāng)我們訪問span表示的整體或部分內(nèi)存時,內(nèi)部的索引器會按照下面的算法運算指針(偽代碼):

      ref T this[int index]
      {
          get => ref ((ref reference + byteOffset) + index * sizeOf(T));
      }
      

      整個變化的過程,如圖所示:

      上面的動畫非常清楚了吧,舊span整合它的引用和偏移成新的span的引用,整個過程并沒有復(fù)制內(nèi)存,也沒有返回相對位置上存在的副本,而是直接返回實際存儲位置的引用,因此性能非常高,因為新span獲得并更新了引用,所以垃圾回收器(GC)知道如何處理新的span,從而獲得了.Net至關(guān)重要的安全保障,并且內(nèi)部還會自動執(zhí)行邊界檢查確保內(nèi)存安全,而這些都是span內(nèi)部默默完成的,開發(fā)人員根本不用擔(dān)心,非托管世界依然美好。
      正是由于span的高性能,目前很多基礎(chǔ)設(shè)施都開始支持span,甚至使用span進(jìn)行重構(gòu),比如:System.String.Substring方法,我們都知道此方法是非常消耗性能的,首先會創(chuàng)建一個新的字符串,然后再從原始字符串中復(fù)制字符集給它,而使用span可以實現(xiàn)Non-Allocating、Zero-coping,下面是我做的一個基準(zhǔn)測試:

      使用String.SubString和Span.Slice分別截取長度為10和1000的字符串的前一半,從指標(biāo)Mean可以看出方法SubString的耗時隨著字符串長度呈線性增長,而Slice幾乎保持不變;從指標(biāo)Allocated Memory/Op可以看出,方法Slice并沒有被分配新的內(nèi)存,實踐出真知,可以預(yù)見Span未來將會成為.Net下編寫高性能應(yīng)用程序的重要積木,應(yīng)用前景也會非常地廣,微服務(wù)、物聯(lián)網(wǎng)、云原生都是它發(fā)光發(fā)熱的好地方。

      基準(zhǔn)測試示例

      總結(jié)

      從技術(shù)的本質(zhì)上看,Span<T>是一種ref-like type類似引用的結(jié)構(gòu)體;從應(yīng)用的場景上看,它是高性能的sliceable type可切片類型;綜上所訴,Span是一種類似于數(shù)組的結(jié)構(gòu)體,但具有創(chuàng)建數(shù)組一部分視圖,而無需在堆上分配新對象或復(fù)制數(shù)據(jù)的超能力
      看完本篇博客,如果理解了Span的What、Why、How,那么作者布道的目的就達(dá)到了,不懂的同學(xué)建議多讀幾遍,下一篇,我將會進(jìn)一步暢談Span的脾氣秉性,讓大家能夠安全高效地使用好它。

      補充

      從評論區(qū)交流發(fā)現(xiàn),有的同學(xué)誤解了span,表面上認(rèn)為只是對指針的封裝,從而繞過unsafe帶來的限制,避免開發(fā)人員直接面對指針而已,其實不是,下面我們來看一個示例:

      var nativeMemory = Marshal.AllocHGlobal(100);
      Span<byte> nativeSpan;
      unsafe {
      nativeSpan = new Span<byte>(nativeMemory.ToPointer(), 100);
      }
      SafeSum(nativeSpan);
      Marshal.FreeHGlobal(nativeMemory);
      
      // 這里不關(guān)心操作的內(nèi)存類型,即不用為一種類型寫一個重載方法,就好比上面的設(shè)計一樣。
      static ulong SafeSum(Span<byte> bytes) {
      ulong sum = 0;
      for(int i=0; i < bytes.Length; i++) {
      sum += bytes[i];
      }
      return sum;
      }
      
      

      看到了嗎,并沒有繞過unsafe,以前該如何用,現(xiàn)在還是一樣的,span解決的是下面幾點:

      1. 高性能,避免不必要的內(nèi)存分配和復(fù)制
      2. 高效率,它可以為任何具有無復(fù)制語義的連續(xù)內(nèi)存塊提供安全和可編輯的視圖,極大地簡化了內(nèi)存操作,即不用為每一種內(nèi)存類型操作寫一個重載方法
      3. 內(nèi)存安全,span內(nèi)部會自動執(zhí)行邊界檢查來確保安全地讀寫內(nèi)存,但它并不管理如何釋放內(nèi)存,而且也管理不了,因為所有權(quán)不屬于它,希望大家要明白這一點

      它的目標(biāo)是未來將成為.Net下編寫高性能應(yīng)用程序的重要積木。

      最后

      如果有什么疑問和見解,歡迎評論區(qū)交流。
      如果你覺得本篇文章對您有幫助的話,感謝您的【推薦】。
      如果你對.NET高性能編程感興趣的話可以【關(guān)注我】,我會定期的在博客分享我的學(xué)習(xí)心得。
      歡迎轉(zhuǎn)載,請在明顯位置給出出處及鏈接

      延伸閱讀

      https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T

      https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Span.cs

      https://blogs.msdn.microsoft.com/dotnet/2017/11/15/welcome-to-c-7-2-and-span

      https://docs.microsoft.com/zh-cn/dotnet/api/system.span-1?view=netcore-2.2

      https://blog.marcgravell.com/2017/04/spans-and-ref-part-2-spans.html

      https://github.com/dotnet/corefxlab/blob/master/docs/specs/span.md

      https://blog.marcgravell.com/2017/04/spans-and-ref-part-1-ref.html

      https://channel9.msdn.com/Events/Connect/2017/T125

      https://msdn.microsoft.com/en-us/magazine/mt814808

      https://github.com/dotnet/BenchmarkDotNet/pull/492

      https://github.com/dotnet/coreclr/issues/5851

      https://github.com/joeduffy/slice.net

      https://adamsitnik.com/Span

      posted @ 2018-11-28 08:56  justmine  閱讀(21351)  評論(105)    收藏  舉報
      主站蜘蛛池模板: 99RE8这里有精品热视频| 美女无遮挡免费视频网站| 亚洲中文字幕无码一久久区| 国产精品人妻熟女男人的天堂| 国产亚洲色视频在线| 日本少妇被黑人xxxxx| www国产精品内射熟女| 亚洲一区二区三区色视频| 日韩欧美aⅴ综合网站发布| 国产影片AV级毛片特别刺激| 色综合天天综合网中文伊| 在线观看无码不卡av| 亚洲国产精品成人无码区| 被灌满精子的少妇视频| 久热爱精品视频线路一| 亚洲人成网站77777在线观看| 日韩精品人妻av一区二区三区| 亚洲a∨国产av综合av| 最新精品国偷自产在线美女足| 久久亚洲精品11p| 亚洲av午夜福利精品一区二区 | 精品国产一区av天美传媒| 好了av四色综合无码| 色五月丁香五月综合五月| 日本高清中文字幕免费一区二区| 99re在线视频观看| 色婷婷久久综合中文久久一本 | 国产精品任我爽爆在线播放6080| 亚洲天堂一区二区成人在线| 麻豆a级片| 丝袜美腿亚洲综合在线观看视频| 国产不卡一区二区三区视频| 91福利国产成人精品导航| 久热这里只有精品12| 国产精品国产精品国产精品| 777奇米四色成人影视色区| 乳源| 中文字幕人妻有码久视频| 67194熟妇在线直接进入| 国产色无码精品视频免费| 日本一区二区中文字幕久久|