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

歡迎訪問 生活随笔!

生活随笔

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

C#

编写高性能的C#代码(三)使用SPAN

發布時間:2023/12/4 C# 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 编写高性能的C#代码(三)使用SPAN 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文來自互聯網,由長沙DotNET技術社區編譯。如譯文侵犯您的署名權或版權,請聯系小編,小編將在24小時內刪除。

作者介紹:

史蒂夫·戈登(Steve Gordon)是Microsoft MVP,Pluralsight的作者,布萊頓(英國西南部城市)的高級開發人員和社區負責人。

編寫高性能的C#代碼(三)使用SPAN?

這篇文章繼續了我有關編寫高性能C#代碼的系列文章[1]。在本文中,我們將通過介紹Span?類型從上兩篇文章繼續,并通過將其轉換為基于Span的版本來重構一些現有代碼。我們將使用Benchmark.NET比較這些方法并驗證我們的更改是否改進了代碼。

如果您想遵循示例代碼,可以在GitHub上找到[2]

什么是SPAN??

Span是C#7.2引入的一種新類型,在.NET Core 2.1運行時中受支持。現有的.NET Standard 1.0運行時都有一個.NET Standard實現,但是在.NET Core中,我將重點介紹運行時更改,以支持可能的最佳版本,也稱為“fast span”。

Span提供對內存連續區域的類型安全訪問。該內存可以位于堆,堆棧上,甚至可以由非托管內存組成。Span具有相關的類型ReadOnlySpan?,該類型提供內存中數據的只讀視圖。ReadOnlySpan可用于查看不可變類型(例如字符串)占用的內存。我更喜歡將Span視為進入某些現有內存的窗口,而不管其分配在何處。

圖片

在上圖中,Span?引用一些已經分配的連續內存。現在,我們在該內存上有了一個窗口。

Span?被定義為引用結構,這意味著它僅限于僅在堆棧上分配。這減少了一些潛在的用例,例如將其存儲為類中的字段或在異步方法中使用它。這些限制可以通過使用類似的新型Memory來解決,我們將在以后的文章中介紹它。引用結構設計的主要原因是要確保在使用Span時,我們不會引起其他堆分配。這是它支持高性能代碼路徑中如此高度優化的用例的原因之一。

我將避免為這篇文章過多地介紹實現細節(畢竟這是一篇介紹),而將重點放在一個示例中,我們可能在哪里使用它以及它如何影響我們的基準。

如果您想閱讀有關Span的更多詳細信息,我建議以下鏈接:

?Span?結構[3]?有關Span的所有信息:探索新的.NET主體[4]?C#7.2:了解Span[5]?Span By Adam Sitnik[6]

加快現有代碼的速度并減少分配

在上一篇文章中,我們對一些代碼進行了基準測試,這些代碼用于從全名字符串中“解析”姓氏。通過Benchmark.NET,我們確定該方法需要125.8 ns的時間運行,并且每次運行分配160個字節。

在使用基于Span?的方法進行重構之前,我希望這是一個公平的競爭,因此我將首先不使用Span?來優化代碼。這有望成為一個很好的例子,因為它著重指出,即使不使用Span之類的新功能,也可以通過對正在執行的工作進行一些思考來優化現有代碼。

當前代碼在任何空格上分割字符串,這將組成一個字符串數組。如果考慮到這一點,我們將分配一個數組,在使用名稱“ Steve J Gordon”的情況下,這樣做時將分配三個較小的字符串“ Steve”,“ J”和“ Gordon”。正如我們在基準測試中所看到的那樣,這會導致分配160個字節。

對于查找姓氏的要求,我們不在乎存儲名稱的所有部分,而只是存儲我們希望是姓氏的最后一部分。請注意,在此示例中,我忽略了多詞姓氏等情況!

讓我們向NameParser添加另一個方法,該方法而不是拆分字符串,而是獲取最后一個空格字符的索引,并使用該方法獲取代表姓氏的子字符串。

public string GetLastNameUsingSubstring(string fullName){var lastSpaceIndex = fullName.LastIndexOf(" ", StringComparison.Ordinal);return lastSpaceIndex == -1? string.Empty: fullName.Substring(lastSpaceIndex + 1);}

首先,我們獲取全名字符串中最后一次出現空格的索引。如果它為-1,則找不到任何空格,因此我們將返回一個空字符串作為默認結果。如果找到索引,則使用Substring方法提取姓氏并將其返回。

我們稍后會將該版本包含在我們的基準測試中。但是,實際上,值得在進行代碼改進的每個迭代時對其進行測試,以驗證您是在改進方面還是使它們變得更糟。

使用SPAN?

讓我們看看這次如何使用Span?重新編寫此代碼。在高性能需求旺盛的場景中,我們既要提高速度又要減少代碼中的內存分配。

public ReadOnlySpan<char> GetLastNameWithSpan(ReadOnlySpan<char> fullName){var lastSpaceIndex = fullName.LastIndexOf(' ');return lastSpaceIndex == -1 ? ReadOnlySpan<char>.Empty : fullName.Slice(lastSpaceIndex + 1);}

首先要注意的是,方法參數“ fullName”現在的類型為ReadOnlySpan?。某些類型(例如字符串)可以隱式轉換為chars的ReadOnlySpan,因此此方法簽名可以正常工作。現在,返回類型也是ReadOnlySpan。

首先,以與上面的優化代碼非常相似的方式,我們尋找空格字符的最后一個索引。

同樣,如果其值為-1,則我們找不到空格,并且將返回空的ReadOnlySpan?結果。

如果找到空格字符,我們現在可以使用Span的一種功能,即“切片”(Slice)。

切片是一項非常強大的操作,我們可以將現有的Span和“切片”放到更緊密的窗口中。切片時,我們為切片指定起始位置的索引,并為切片指定終止位置的長度。省略長度會導致從起始位置到Span結束的切片。

切片是一種低成本的操作,因為我們不復制任何內容,而只是創建一個新的Span,該Span表示一個進入現有內存范圍子集的窗口。

圖片

在上圖中,我們可以創建原始Span的Slice來查看其中的5個元素,而無需分配原始內存的任何其他副本。

在新的基于Span的代碼中,我們從空格字符后的索引處開始獲取fullName的一部分。由于我們未指定長度,因此此切片將運行到現有Span的末尾。

對Span進行切片后,會在切片的部分上產生一個新的Span,然后將其作為方法的結果返回。

至此,我們有兩個潛在的改進代碼版本,一個使用Substring,另一個使用Span?。讓我們更新基準并比較結果。

衡量改進基準

添加兩個新基準后,基準類現在如下所示:

[RankColumn][Orderer(SummaryOrderPolicy.FastestToSlowest)][MemoryDiagnoser]public class NameParserBenchmarks{private const string FullName = "Steve J Gordon";private static readonly NameParser Parser = new NameParser();[Benchmark(Baseline = true)]public void GetLastName(){Parser.GetLastName(FullName);}[Benchmark]public void GetLastNameUsingSubstring(){Parser.GetLastNameUsingSubstring(FullName);}[Benchmark]public void GetLastNameWithSpan(){Parser.GetLastNameWithSpan(FullName);}}

我們定義了三個基準,每個基準在NameParser中采用不同的方法。運行基準測試在我的計算機上給出以下結果…

圖片

此列表中的最后一項是我們原始的GetLastName方法。因為我們要求獲得排名結果,并且此方法運行的最慢,所以它在最后顯示出來。

這次大約花了125ns的時間運行,當然仍然分配了160個字節。

第二快的是我們嘗試在不使用Span?的情況下改進代碼的情況,該代碼使用Substring。此代碼比原始方法快大約3倍。重要的是,我們現在將分配減少到只有40個字節。這說明了我們在調用子字符串時要分配的姓氏字符串。

總的贏家是基于Span?的方法。這比我們的原始代碼快10倍,比基于子字符串的方法快2.8倍。

這里真正重要的是,因為我們要對Span進行切片以查找姓氏的位置,并且還返回Span作為方法的輸出,所以我們永遠不會分配新的字符串。通過已分配的內存狀態(現在為空)可以明顯看出這一點。

圖片

對于單個調用,節省的160個字節(或子字符串方法節省40個字節)并不龐大,但是在特定場景下上,節省的費用加起來了。

如果此代碼需要在我維護的每天處理約2000萬條消息的數據處理服務中運行,那么我們每天將節省3.2 GB的分配。這些可能是短暫的分配,但是即使如此,它們仍將導致垃圾回收。根據估算的Gen 0 / 1k操作數(譯者注,是指0代回收,每次回收1k字節),原始代碼每天將觸發2,000個操作,共506個GC。

這是CPU時間和暫停時間,我們可以通過避免分配任何資源來幫助減少時間。

摘要

在本文中,我們研究了新的Span?類型,并使用它重構了一些代碼以實現最佳性能。最初,Span聽起來可能有點復雜,但正如我希望我已經展示的那樣,在本示例中使用它非常簡單。

謝謝閱讀!

如果您想了解有關高性能.NET和C#代碼的更多信息,可以在此處[7]查看我的完整博客文章系列。

References

[1]?有關編寫高性能C#代碼的系列文章:?https://www.stevejgordon.co.uk/motivations-for-writing-high-performance-csharp-code
[2]?示例代碼,可以在GitHub上找到:?https://github.com/stevejgordon/BenchmarkAndSpanExample
[3]?Span?結構:?https://docs.microsoft.com/en-us/dotnet/api/system.span-1?view=netcore-2.2
[4]?有關Span的所有信息:探索新的.NET主體:?https://msdn.microsoft.com/en-us/magazine/mt814808.aspx
[5]?C#7.2:了解Span:?https://channel9.msdn.com/Events/Connect/2017/T125
[6]?Span By Adam Sitnik:?https://adamsitnik.com/Span/
[7]?在此處:?https://www.stevejgordon.co.uk/writing-high-performance-csharp-and-dotnet-code

總結

以上是生活随笔為你收集整理的编写高性能的C#代码(三)使用SPAN的全部內容,希望文章能夠幫你解決所遇到的問題。

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