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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

实际体验SpanT 的惊人表现

發布時間:2023/12/4 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 实际体验SpanT 的惊人表现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

最近做了一個過濾代碼塊功能的接口。就是獲取一些博客文章做文本處理,然后這些博客文章的代碼塊太多了,很多重復的代碼關鍵詞如果被拿過來處理,那么會對文本的特征表示已經特征選擇會有很大的影響。所以需要將這些代碼塊的部分給過濾掉。過濾起來很簡單,就是找代碼塊的html 標記,然后將html標記之間的內容給刪除就可以了。代碼塊的html標記一般都是<pre></pre>

我使用了String,Regex,StringBuilder,Span<T>這些不同的方法來實現這個功能,利用BenchMarks比較它們之間的性能差距。

BenchMarks

要對比不同代碼之間的性能差距,還是不用StopWatch來計算消耗時間,這樣簡單的方法,而是使用BenchMarksDotNet包:一個專業的.net core下測試程序性能的工具包。

BenchMarksDotNet的github地址

這里簡短介紹下BenchMarksDotNet的使用:

首先新建一個需要測試的類:FilterCodeBlocks?,并在類中寫上被測試的方法:FilterCodeBlockByString

public class FilterCodeBlocks{public string FilterCodeBlockByString(string content){return content;}}

然后新建一個類:?FilterCodeBlocksBenchMark

using System; using System.IO; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Order;namespace QuickSortBenchMarks {[RankColumn][Orderer(SummaryOrderPolicy.FastestToSlowest)][MemoryDiagnoser]public class FilterCodeBlocksBenchmarks{FilterCodeBlocks FilterCodeBlocks = new FilterCodeBlocks();[Benchmark]public void FilterByString(){FilterCodeBlocks.FilterCodeBlockByString(s);}} }

最后在入口Progam.cs中 寫上

class Program{static void Main(string[] args){var summary = BenchmarkRunner.Run<FilterCodeBlocksBenchmarks>();}}

執行dotnet build -c Release?然后?dotnet yourproject.dll?就可以看見BenchMarks測試效果.

鋪墊好東西,現在開始進入正題。

使用 string

首先,直接用string 操作。由于測試博文可能會比較長,會有比較多的代碼塊。所以我的思路是,while(true)?去尋找代碼塊標記,并使用string 的尋址:?indexOf()?, 拼接:+=?和 剪切:Substring()?完成代碼塊的過濾。過程也很簡單。這只是解決問題的一種方法,這篇文章的目的不是尋找最優解決方法,而是比較發現使用不同的 "工具" 之間的巨大性能差距。

private static string _startTag = "<pre";private static string _endTag = "</pre>";private static int _startTagLength => _startTag.Length;private static int _endTagLength => _endTag.Length;public FilterCodeBlocks(){}public string FilterCodeBlockByString(string content){string result = "";while (true){var startPos = content.IndexOf(_startTag, StringComparison.CurrentCulture);if (startPos == -1)break;var content2 = content.Substring(startPos + _startTagLength, content.Length - startPos - _startTagLength);var endPos = content2.IndexOf(_endTag, StringComparison.CurrentCulture);result += content.Substring(0, startPos);content = content2.Substring(endPos + _endTagLength, content2.Length - endPos - _endTagLength);}result += content;return result;}

一開始選取了比較短的文本進行測試 ,可以直接寫在程序中:

[RankColumn][Orderer(SummaryOrderPolicy.FastestToSlowest)][MemoryDiagnoser]public class FilterCodeBlocksBenchmarks{FilterCodeBlocks FilterCodeBlocks = new FilterCodeBlocks();public static string s = "<p>我們通過IndexWriterConfig 可以設置IndexWriter的屬性," +"已達到我們希望構建索引的需求,這里舉一些屬性,這些屬性可以影響到IndexWriter寫入索引的速度:" +"</p>\n<div class=\"cnblogs_code\">\n<pre>IndexWriterConfig.setRAMBufferSizeMB" +"(<span style=\"color: #0000ff;\">double</span><span style=\"color: #000000;\">);" +"\nIndexWriterConfig.setMaxBufferedDocs(</span><span style=\"color: #0000ff;\">int</span><span " +"style=\"color: #000000;\">);\nIndexWriterConfig.setMergePolicy(MergePolicy)</span></pre>\n</div>\n<p>" +"setRAMBufferSizeMB()&nbsp;是設置";[Benchmark]public void FilterByString(){FilterCodeBlocks.FilterCodeBlockByString(s);}}

按照上述的方法,運行dll 得出 使用string 相關方法的性能。

平均處理時間?48微秒?分配內存?1.41kb,看來效果也是不錯的,我感覺上面的代碼中方法也是大家都會經常使用的方法。

接下來?.NET Core 2.1的新特性:?Span?隆重登場!

Span< T >

What is a Span< T >?

Span< T > :?結構體值類型?。相當于C++ 中的指針,它是一段連續內存的引用,也就是一段連續內存的首地址。有了Span< T >,我們就可以不在unsafe的代碼塊中寫指針了。Span< char > 相對于 string 也就具有很大的性能優勢。

舉個栗子:?string.Substring()?函數,實際上是在堆中額外創建了一個新的 string 對象,把字符 copy 過去,再返回這個對象的引用。而相對應的 Span< T > 的Slice()?函數則是直接在內存中返回子串的首地址引用,此過過程幾乎不分配內存,并且十分高效。

后面的優化也是使用Span< T > 的Slice()?代替了 string 的SubString()?。

簡單看下 Span< T > 的源碼,就可以窺見 Span< T > 的奧秘:

public readonly ref partial struct Span<T>{/// <summary>A byref or a native ptr.</summary>internal readonly ByReference<T> _pointer;/// <summary>The number of elements this Span contains.</summary>private readonly int _length;....public Span(T[] array){if (array == null){this = default;return; // returns default}if (default(T) == null && array.GetType() != typeof(T[]))ThrowHelper.ThrowArrayTypeMismatchException();_pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()));_length = array.Length;}}

Span< T > 內部主要就是一個ByReference< T >?類型的對象,實際上就是ref T: 一個類型的引用,它和C 的int*?char*?如出一折。Span < T > 也就是建立 ref 的基礎上。

限定長度:?_length?,就像 C 中定義指針,在使用前需要?malloc?或者?alloc?分配固定長度的內存。關于Span< T > 更多詳細知識:

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

使用 Span< T > 優化

將上述 string 代碼使用 Span< char > 優化一下

public string FilterCodeBlockBySpanAndToString(ReadOnlySpan<char> content){string result = "";ReadOnlySpan<char> contentSpan2 = new ReadOnlySpan<char>();int startPos = 0;int endPos = 0;ReadOnlySpan<char> startTagSpan = _startTag.AsSpan();ReadOnlySpan<char> endTagSpan = _endTag.AsSpan();while (true){startPos = content.IndexOf(startTagSpan);if (startPos == -1)break;contentSpan2 = content.Slice(startPos + _startTagLength, content.Length - startPos - _startTagLength);endPos = contentSpan2.IndexOf(endTagSpan);result += content.Slice(0, startPos).ToString();content = contentSpan2.Slice(endPos + _endTagLength, contentSpan2.Length - endPos - _endTagLength);}result += content.ToString();return result;}

這里?ReadOnlySpan<char>?是 Span< char > 的只讀類型。使用Slice?代替SubString?。上述代碼我依然返回的是 string。為了得到 string,我不惜使用Span< T > 的ToString()?函數,在我印象中,這個操作會把Span?的優勢給拉回起跑線。

接下來看測試結果:

真是大吃一驚,平均消耗時間,居然少了?48000 納秒,Span< T > 只是 string 的不到百分之一消耗。內存消耗減少了一半

Span< T >果然名不虛傳,正如前面所說的SubString?和Slice?之間的性能差距。

Span< T > 的特色

雖然Span< T > 的性能十分出色 ,但是 string 有太多完善的接口,string 是為了簡化你的代碼讓你更加舒服的使用字符串,所以犧牲了性能。因此 在對計算機消耗要求十分的嚴苛的情況下,嘗試使用Span< T > ,大多數情況下,簡短的string 已經能滿足需求。我的認知下的Span< T >的特色:

  • Span< T >的定義方法多種多樣,可以直接 ( i ) 像定義數組那樣 :?Span<int> a = new int[10];?( ii ) 在構造函數中直接傳入 數組(指針+長度)Span<T> a = new Span<T>(T[]),Span<T> a = new Span<T>(void*,length)?; ( iii )可以直接在棧中分配內存:Span<char> a = stackalloc char[10];?在C# 8.0中才可以,這樣的寫法真是高大上。

  • Span< T > 只能存在于棧中,而不能放在堆中。因為 ( i ) GC 在堆中很難跟蹤這些指針, ( ii ) 在堆中會出現多線程, 如果兩個線程的兩個Span< T >指向了同一個地址,那就糟了。

  • 可以使用 Memory< T > 代替 Span< T >在堆中使用。

  • 所有 string 的接口都可以用 Span< char > 來實現,這似乎又回到了原始的C語言時代。

  • Span < T > 有個兄弟叫 ReadOnlySpan< T > 。

到這里還不能結束Span< T >的性能評測。因為在大量字符串處理中還有個隱藏的實力派:正則表達式?Regex

正則表達式

如果我們使用正則表達式呢,它的性能會是如何呢?

正則表達式的實現:

private static Regex _codeTag = new Regex("(<pre(.*?)>)(.|\n)*?(</pre>)", RegexOptions.Compiled);public string FilterCodeBlocByRegex(string content){return _codeTag.Replace(content, string.Empty);}

真是簡短的讓人看著就舒服。正則表達式的長處是在大文本處理,所以我決定直接將字符串變成100篇博客的內容加在一起。下面就是測試結果:

Incredible! 正則表達式 真的是一匹黑馬,直逼Span< T >,時間消耗僅為10.68ms,內存消耗只有7.69MB。難得的是它的內存消耗也比Span< T >低。

為什么Regex會有這么好的表現呢?翻閱一下源碼,原來如此!

private static string Replace(MatchEvaluator evaluator, Regex regex, string input, int count, int startat) {....Span<char> charInitSpan = stackalloc char[ReplaceBufferSize];var vsb = new ValueStringBuilder(charInitSpan); }

在.net core 2.2?中,Regex的 Replace 內部用了 Span< char > 重新實現。看來,正則表達式的高性能表現 和 Span 不無關系。

根據園友的評論,Regex 以前的版本,也是通過指針來進行操作,我也實驗了 .net standard的Regex , 二者效率差不多。

Span < T > 很優秀,但是為了解決 string 的性能問題,C# 早早就有了?StringBuilder?。于是我讓了字符串處理界的大師:StringBuilder, 來助 Span< T > 一臂之力。

StringBuilder + Span< T >

public string FilterCodeBlockBySpanAndStringBuilder(ReadOnlySpan<char> content){var result = new StringBuilder(content.Length);var contentSpan2 = new ReadOnlySpan<char>();var startPos = 0;var endPos = 0;var startTagSpan = _startTag.AsSpan();var endTagSpan = _endTag.AsSpan();while (true){startPos = content.IndexOf(startTagSpan);if (startPos == -1)break;contentSpan2 = content.Slice(startPos + _startTagLength, content.Length - startPos - _startTagLength);endPos = contentSpan2.IndexOf(endTagSpan);result.Append(content.Slice(0, startPos));content = contentSpan2.Slice(endPos + _endTagLength, contentSpan2.Length - endPos - _endTagLength);}result.Append(content);return result.ToString();}

將原先的 字符串拼接變成了 StringBuilder 的?append函數,而且減少了我心心念念的ToString()次數。在 .net core 2.2 中StringBuilder的內部也有 Span< T >的身影。

Append 函數可以直接接受Span< T >的參數。接下來看看武裝到牙齒的Span< T >性能如何。

unbelievable ! 使用 StringBuilder 的Span< T >時間消耗居然只有?867.1微妙,內存消耗只有1.7MB?,在各個方面都技壓群雄。又是百分之一的消耗。

實際上 StringBuilder的內部操作字符串的 是一個 char 數組,它的 Apend 的性能如此之高,還是因為內部使用了指針。

unsafe{fixed (char* valuePtr = value)fixed (char* destPtr = &chunkChars[chunkLength]){string.wstrcpy(destPtr, valuePtr, valueLen);}}

StringBuilder 只能支持字符串,但是Span< T >可是泛型的哦。不過,程序中最消耗CPU的大都是一些字符串的處理。

結語

在實際中體驗了Span< T >的驚人表現。同時 .NET Core 在Span< T >加入之后,各個地方都有性能的提升,比如說Regex。真是讓開發者何其幸哉。

在Regex 中的源代碼,我看到了一個 ValueStringBuilder 一個內部的結構體,只能在System/Text 的內部中使用。它是一個結構體!它的構造函數可以直接傳入 Span< char >,我將它 copy 出來,代替StringBuilder , 時間消耗不分伯仲,但是內存消耗又減少了一半!。這應該是極致的性能表現。鑒于篇幅原因就不展開了。

可以在?這里: https://github.com/SilentCC/MyTestBenchMarks?看到ValueStringBuilder,以及完整的代碼。

總結

以上是生活随笔為你收集整理的实际体验SpanT 的惊人表现的全部內容,希望文章能夠幫你解決所遇到的問題。

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