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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > C# >内容正文

C#

通俗易懂,C#如何安全、高效地玩转任何种类的内存之Span

發(fā)布時(shí)間:2023/12/4 C# 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 通俗易懂,C#如何安全、高效地玩转任何种类的内存之Span 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

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

為什么要使用指針,什么時(shí)候需要使用它?

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

what - 痛點(diǎn)是什么?

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

  • 托管內(nèi)存(managed memory )

    var mangedMemory = new Student();

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

  • 棧內(nèi)存(stack memory )

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

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

  • 本機(jī)內(nèi)存(native memory )

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

  • 拋磚引玉 - 痛點(diǎn)

    首先我們設(shè)計(jì)一個(gè)解析完整或部分字符串為整數(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個(gè)重載方法。

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

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

    how - span如何解決這個(gè)痛點(diǎn)?

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

  • 托管內(nèi)存(managed memory )

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

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

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

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

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

    why - 為什么span能解決這個(gè)痛點(diǎn)?

    淺析span的工作機(jī)制

    先來窺視一下源碼:

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

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

    整個(gè)變化的過程,如圖所示:

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

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

    基準(zhǔn)測試示例

    總結(jié)

    看完本篇博客,應(yīng)該對Span的What、Why、How了如指掌了,那么我的目的就達(dá)到了,不懂的同學(xué)可以多讀幾遍,下一篇,我將會(huì)暢談Span的應(yīng)用場景、優(yōu)缺點(diǎn),讓大家能夠安全高效地使用好它,大家也可以在評論留言自己的應(yīng)用場景,我會(huì)在寫下一篇博客時(shí)多多參考。

    補(bǔ)充

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

    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)存類型,即不用為一種類型寫一個(gè)重載方法,就好比上面的設(shè)計(jì)一樣。

    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要解決的是下面兩點(diǎn):

  • 高性能,避免不必要的內(nèi)存分配。

  • 高效率,不用為每一種內(nèi)存類型操作寫一個(gè)重載方法。

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

    最后

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

    延伸閱讀

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

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

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

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

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

    https://adamsitnik.com/Span

    做一個(gè)有底蘊(yùn)的軟件工程師


    相關(guān)文章:

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

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

    • 有關(guān)C# 8.0、.NET Framework 4.8與NET Standard 2.1的一個(gè)說明

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


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

    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。