主頁 > .NET開發 > dotnet 6 使用 string.Create 提升字串創建和拼接性能

dotnet 6 使用 string.Create 提升字串創建和拼接性能

2022-03-24 06:16:35 .NET開發

本文告訴大家,在 dotnet 6 或更高版本的 dotnet 里,如何使用 string.Create 提升字串創建和拼接的性能,減少拼接字串時,需要額外申請的記憶體,從而減少記憶體回收壓力

本文也是跟著 Stephen Toub 大佬學性能優化系列博客之一,這是 Stephen Toub 大佬在給 WPF 做的性能優化里面其中的一個小點,只是剛好這個優化點,是 Stephen Toub 大佬參與設計(預計是主導)和進行開發的,此優化點需要修改 Roslyn 內核,撰寫分析器,以及在 dotnet runtime 層進行支持才可以做到的優化,在過去完成了從 Roslyn 到分析器到 runtime 的支持之后,就到了應用框架層的支持了,這就是 Stephen Toub 大佬會在 WPF 倉庫活躍的其中一個原因了

歪個樓,大家知道 dotnet 的各個層之間的關系吧,在 dotnet 里面,各個部分的角色是:

  • Roslyn: 編譯器內核層
  • Runtime: 提供運行時的支持,廣義的運行時,包括了執行引擎和基礎庫
  • WPF: 應用代碼框架層

在 WPF 上方就是業務代碼邏輯了

在 WPF 倉庫里 Stephen Toub 大佬的改動代碼可以從 Remove some unnecessary StringBuilders by stephentoub · Pull Request #6275 · dotnet/wpf 找到,這就是本文的例子代碼了

在 dotnet 6 里面,新提供了 string.Create 方法的兩個新多載方法,此兩個多載方法簽名分別如下

第一個多載方法:

public static string Create (IFormatProvider? provider, Span<char> initialBuffer, ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler handler);

以上的三個引數的說明如下:

  • provider: 一個提供區域性特定的格式設定資訊的物件,
  • initialBuffer: 初始緩沖區,可用作格式設定操作的一部分的臨時空間, 此緩沖區的內容可能會被覆寫,
  • handler: 通過參考傳遞的內插字串,

第二個多載方法:

public static string Create (IFormatProvider? provider, ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler handler);

第二個多載方法只是將第一個方法的 Span<char> initialBuffer 干掉而已

本文核心和大家聊的就是第一個多載方法

為什么這兩個方法只有在 dotnet 6 或更高版本才能使用?為什么低版本的不能使用?如本文開始所說,這是因為這兩個方法需要從 Roslyn 改到 dotnet runtime 才能支持,那為什么需要改那么多才能支持呢?因為這兩個方法別看起來簡單,實際上用到了 Roslyn 的黑科技,當然了用上了 Roslyn 黑科技,就可以讓你告訴老師們,你的知識又需要更新了

敲黑板,第一個知識更新點是內插字串,有趣的是在 C# 6.0 提出的內插字串的知識點,剛好在 dotnet 6 的時候進行更新,別混了哦,這里說的 C# 版本和 dotnet 的版本可是兩回事哦,如以下的內插字串,你猜猜這是什么

  $"lindexi is {doubi}"

在 dotnet 6 或更低的版本,你可以聽從老師的話,說這是一個 string.Format 的語法優化而已,和以下的代碼是完全等價的

 string.Format("lindexi is {0}", doubi);

當然了,這么簡單的代碼我可沒有開IDE來寫,如果語法寫錯了,還請大家忽略吧

但是在 dotnet 6 或更高的版本,這些知識就需要更新了哈,看到了內插字串,可不一定是 string.Format 的語法優化,還可以是 System.Runtime.CompilerServices.DefaultInterpolatedStringHandler 型別的創建哦

官方有一篇博客,嗯,又是 Stephen Toub 大佬寫的,來告訴大家,這個 DefaultInterpolatedStringHandler 型別的來源以及是如何作業的,詳細請看 String Interpolation in C# 10 and .NET 6 - .NET Blog

簡單來說就是使用內插字串時,在 C# 10 和 dotnet 6 之前,將會額外創建一些物件,這些物件將會造成記憶體回收的壓力,嗯,只是造成壓力而已,不用擔心,咱996都不怕,一點壓力,沒多少

如下面的代碼,就是一個標準的內插字串的用法

public static string FormatVersion(int major, int minor, int build, int revision) =>
    $"{major}.{minor}.{build}.{revision}";

在 C# 10 和 dotnet 6 之前,經過了構建的代碼,將會拆分以上的語法優化大概為如下代碼

public static string FormatVersion(int major, int minor, int build, int revision)
{
    var array = new object[4];
    array[0] = major;
    array[1] = minor;
    array[2] = build;
    array[3] = revision;
    return string.Format("{0}.{1}.{2}.{3}", array);
}

可以看到,其實這將需要額外多創建了一個 object 陣列,同時在 string.Format 方法里面,還有很多其他的損耗

在 C# 10 和 dotnet 6 同時滿足時,將在構建時,修改為如下結果等價的代碼

public static string FormatVersion(int major, int minor, int build, int revision)
{
    var handler = new DefaultInterpolatedStringHandler(literalLength: 3, formattedCount: 4);
    handler.AppendFormatted(major);
    handler.AppendLiteral(".");
    handler.AppendFormatted(minor);
    handler.AppendLiteral(".");
    handler.AppendFormatted(build);
    handler.AppendLiteral(".");
    handler.AppendFormatted(revision);
    return handler.ToStringAndClear();
}

這個 DefaultInterpolatedStringHandler 是一個結構體物件,根據一個完全不對的知識,結構體是在堆疊上分配的,以上的代碼將除了回傳的字串之外,不會需要額外的記憶體申請,雖然知識完全是錯的,不過結果是對的哈,辟謠時間:結構體可以是在堆疊上分配,也可以是在堆上分配的,對于大部分的區域變數創建的結構體來說,此結構體就是在堆疊上分配的,至少,以上的代碼就是在堆疊上分配了一個 DefaultInterpolatedStringHandler 結構體物件,由于堆疊的記憶體是固定且明確的,可以認為用到 堆疊 上的記憶體就不屬于額外申請的記憶體,再因為堆疊的空間,將會在方法執行完成之后,自動堆疊回收,也就沒有了記憶體回收壓力,相當于此方法執行完成之后,此方法內用到的堆疊空間,都會抹掉,自然就不需要算記憶體回收了,當然了,本文的主角可不是堆疊記憶體,細聊下去,我預計還能吹很久,還是回到本文主題吧,大家就只需要記得,以上的代碼超級超級省記憶體分配資源

以上的代碼,分配的物件,只有一個字串,沒錯,就是回傳值的字串

也就是說在 dotnet 6 以及更高的版本,可以讓構建時,將 $ 內插字串,構建成為 DefaultInterpolatedStringHandler 結構體物件,而不需要走 string.Format 方法的邏輯,這是一個很大的優勢,可以讓內插的字串,不需要創建額外的陣列存放引數串列,不需要在 string.Format 方法里面決議字串

但大家又有另外一個疑惑,在使用 DefaultInterpolatedStringHandler 的 ToStringAndClear 方法的時候,難道底層不需要一個快取使用的陣列么?實際上還是有用到的,要不然,還要本文的主角做啥,在 ToStringAndClear 方法里面,實際上是需要用到一個陣列進行快取的,不然的話,代碼還是有點坑,用到了陣列快取,為什么在本文上面還說沒有額外的記憶體分配?別忘了陣列池哦

默認在 DefaultInterpolatedStringHandler 里,將申請 ArrayPool<char>.Shared 一個陣列池的陣列空間來作為快取,在大部分情況下,可以認為這是一個無傷的程序,然而陣列池也不見得每次都有那么空閑,而且,借和還是需要算利息的哦

為了減少利息,減少 CPU 計算的耗時,就到了本文的主角,也就是 string.Create 新加入的多載方法出場的時候

如上文,呼叫 DefaultInterpolatedStringHandler 里,也需要一個快取陣列,那這個陣列,如果也是從堆疊上過來的呢,是不是就更省一些了?沒錯,那如何將從堆疊上的陣列給到 DefaultInterpolatedStringHandler 結構體,這就需要用到本文的主角了

先通過 stackalloc 申請一定的陣列空間,再將陣列空間給到 DefaultInterpolatedStringHandler 結構體,即可實作幾乎所有記憶體的分配邏輯都是在堆疊上分配的,將隨著方法的結束,自動清理垃圾

用法如下:

public static string FormatVersion(int major, int minor, int build, int revision) =>
    string.Create(null, stackalloc char[64], $"{major}.{minor}.{build}.{revision}");

以上的用法屬于高級用法部分,在構建的時候,將自動拆分內插字串為 DefaultInterpolatedStringHandler 結構體,提示將傳入的 stackalloc char[64] 作為緩沖的陣列傳入使用,如此即可實作,除了回傳值的字串,就不需要從堆上額外申請空間,而且在傳入的緩沖陣列夠用的情況下,也不用陣列池里申請快取陣列空間,減少了一借一還的時間損耗,從而達到極高的性能

但,這是高級的用法,還是要需要小心的事項的,第一個就是,咱使用 stackalloc 是在堆疊上分配記憶體空間,分配的大小可要小心哦,如果將堆疊上的空間玩爆了,那就只能再見了,默認分配 512 一下,可以認為是安全的,不過,分配越小越好,剛剛好夠用就好哦,千萬別多打了幾個 0 哦

第二個就是如果傳入的快取空間不足了,那依然會需要從陣列池里申請記憶體空間,而不是進行堆疊空間越界炸掉你的應用,更進一步的說明,有時,咱是無法預估此內插字串所使用的快取大小需要多大的,如果真的難以預估的話,而且實際業務預期也會超過預估的大小,那么使用以上的方法,相當于白申請一段堆疊空間,不如不要

如果實際所需要的字串拼接的快取空間比傳入的 stackalloc 的空間還要更大,那么在 runtime 底層,將拋棄傳入的陣列空間,改用從陣列池申請的空間,因此,傳入 stackalloc 申請的預估的固定大小的陣列,在開發中是安全的,預估的固定大小,如果小了,是不會有邏輯上的問題的

例如使用的內插字串的拼接需要 5000 的 char 陣列空間大小作為快取空間,然而傳入的 stackalloc 申請的空間是 stackalloc char[64] 那顯然不夠用,這是沒有問題的,在底層將重新和陣列池借足夠的空間,不會強行在你的堆疊上分配空間越界的

對于字串來說,還有一個很重要的就是語言文化,例如對于日期來說,美國和中國的文化的日期的字串表示是不相同的,自然在格式化輸出字串時,最好是帶上日期,咱上面的例子只是為了簡單,將 IFormatProvider 傳入空值而已,實際上可以傳入符合你預期的格式化方法,例如無視語言文化的格式化

public static string FormatVersion(int major, int minor, int build, int revision) =>
    string.Create(CultureInfo.InvariantCulture, stackalloc char[64], $"{major}.{minor}.{build}.{revision}");

以上的 CultureInfo.InvariantCulture 將對后續的內插字串進行對應的格式化,如此可以解決很多語言文化的坑

對于咱的應用代碼,如果需要給用戶展示的,最好是根據當地的語言文化進行展示,而對于咱應用里層的計算邏輯,最好是做語言文化無關的,如此才能保持邏輯的符合預期,畢竟詭異的語言格式化還是很多的,采用語言文化無關,可以保持咱應用內計算邏輯符合預期

在 dotnet 6 下,如有使用 string.Create 這兩個新的多載方法進行拼接字串,性能上是比 StringBuilder 更高的

如以下的代碼,是采用 StringBuilder 進行拼接創建字串

StringBuilder stringBuilder = new StringBuilder(64);
stringBuilder.Append(cr.TopLeft.ToString(cultureInfo));
stringBuilder.Append(listSeparator);
stringBuilder.Append(cr.TopRight.ToString(cultureInfo));
stringBuilder.Append(listSeparator);
stringBuilder.Append(cr.BottomRight.ToString(cultureInfo));
stringBuilder.Append(listSeparator);
stringBuilder.Append(cr.BottomLeft.ToString(cultureInfo));
return sb.ToString();

以上代碼是需要多在堆疊上分配一個 StringBuilder 物件的,而且還需要為此物件申請至少一個 64 長度的陣列,而在優化之后,采用 string.Create 的方式,如以下代碼則幾乎除了回傳值的字串之外,就不需要再申請任何的空間

return string.Create(cultureInfo, stackalloc char[128], $"{cr.TopLeft}{listSeparator}{cr.TopRight}{listSeparator}{cr.BottomRight}{listSeparator}{cr.BottomLeft}");

實際上,也不是所有在使用字串拼接的地方,都使用 StringBuilder 都能提升性能,如果字串拼接只是很簡單的兩個字串相加,那么大多數的時候,使用兩個字串相加的性能是大于采用 StringBuilder 拼接的

這就是本文和大家聊的性能優化點,采用 C# 10 和 dotnet 6 配合的字串內插優化方法

博客園博客只做備份,博客發布就不再更新,如果想看最新博客,請到 https://blog.lindexi.com/

知識共享許可協議
本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可,歡迎轉載、使用、重新發布,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用于商業目的,基于本文修改后的作品務必以相同的許可發布,如有任何疑問,請與我[聯系](mailto:[email protected]),

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/448033.html

標籤:.NET技术

上一篇:Blazor 002 : 一種開歷史倒車的UI描述語言 -- Razor

下一篇:網關中間件-Nginx(二)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more