主頁 > .NET開發 > 聊聊 dotnet 7 對 bool 與字串互轉的底層性能優化

聊聊 dotnet 7 對 bool 與字串互轉的底層性能優化

2022-09-29 11:40:49 .NET開發

本文也叫 跟著 Stephen Toub 大佬學性能優化系列,大家都知道在 .NET 7 有眾多的性能優化,其中就包括了對布爾和字串互轉的性能優化,在對布爾和字串的轉換的性能優化上,有著非常巧妙的思路,值得寫篇博客記錄

在 Performance Improvements in .NET 7 - .NET Blog 這篇官方開發博客里面提到了 dotnet 7 的各個方面的性能優化,其中就包括了本身的主角,對 bool 與 string 互轉的性能優化,此優化的核心實作代碼請參閱 https://github.com/dotnet/runtime/pull/64782

在將 "True""False" 字串和 bool 布爾互相轉換時,假定不使用框架內置的轉換方法,那將會如何做呢? 先從字串轉到布爾開始

開始轉換的第一步可以通過字串的長度進行快速的判斷,例如無論是 "True" 還是 "False" 字串,字符的長度都一定是大于 3 的,因此第一步可以嘗試判斷一下,是否大于 3 個字符,如果這一條不滿足,那肯定不能作為布爾的轉換字串了,如果剛好等于 4 個字符長度,那也許就是 "True" 字串了

轉換字串為布爾時,將無視字串的大小寫,人類方便理解的一個版本如下

        internal static bool IsTrueStringIgnoreCase(ReadOnlySpan<char> value)
        {
            return value.Length == 4 &&
                    (value[0] == 't' || value[0] == 'T') &&
                    (value[1] == 'r' || value[1] == 'R') &&
                    (value[2] == 'u' || value[2] == 'U') &&
                    (value[3] == 'e' || value[3] == 'E');
        }

沒錯,就是每個字串都判斷一下,剛好這就是 dotnet 6 所采用的轉換方法,同理,判斷 "False" 也是類似的代碼邏輯

        internal static bool IsFalseStringIgnoreCase(ReadOnlySpan<char> value)
        {
            return value.Length == 5 &&
                    (value[0] == 'f' || value[0] == 'F') &&
                    (value[1] == 'a' || value[1] == 'A') &&
                    (value[2] == 'l' || value[2] == 'L') &&
                    (value[3] == 's' || value[3] == 'S') &&
                    (value[4] == 'e' || value[4] == 'E');
        }

假定要對以上的代碼進行性能優化,可以怎么做呢?似乎我想不到有什么方法

在閱讀了 Stephen Toub 大佬的優化,我才了解到原來還有如此的新思路,通過編碼的方式進行優化,剛好的是在 C# 里面字串的記憶體存盤編碼采用的是 Utf16 編碼,采用 UTF16 編碼的好處在于任意字符所在的記憶體的偏移量是可以完全瞬時確定的,可以快速和二進制進行映射從而提供更多優化的方向,例如剛好一個 UInt64 也就是一個 ulong 的長度就等于 4 個字符,而剛好 "True" 字串就是 4 個字符

同樣感謝遠古的 ASCII 編碼的定義的超級大佬,英文的大小寫轉換只需要取一個 0x20 的或即可讓字符轉換為小寫,無論之前的字符是大寫還是小寫,于是先對輸入的字串內容,從字串取其中前 4 個字符,轉換為 UInt64 的資料,轉換方法如下

internal static bool IsTrueStringIgnoreCase(ReadOnlySpan<char> value)
{
    var theUInt64Value = https://www.cnblogs.com/lindexi/archive/2022/09/29/BinaryPrimitives.ReadUInt64LittleEndian(MemoryMarshal.AsBytes(value));
    // 忽略代碼
}

通過 MemoryMarshal.AsBytes 方法,快速將 value 映射為 byte 陣列,這里必須說明的是,通過 MemoryMarshal.AsBytes 方法是做記憶體映射而不是一個轉換的程序,這是非常快速且安全的一個程序,接著呼叫 BinaryPrimitives.ReadUInt64LittleEndian 方法轉換為 UInt64 的資料

根據上文的 ASCII 編碼規則,為了方便判斷邏輯,將轉換的結果全部轉換為小寫的內容,轉換方法是使用 對每個字符進行 邏輯,代碼如下

var caseValue = https://www.cnblogs.com/lindexi/archive/2022/09/29/theUInt64Value | 0x0020002000200020;

由于字符使用 UTF16 編碼,需要每個使用 0x0020 進行 邏輯,連起來就是 0020_0020_0020_0020 的數值

接著將 true 字串轉換為 UInt64 資料,轉換之后的常量的編碼是 0x65007500720074 這個數字,剛好進行一次 UInt64 判斷即可,耗時可以被忽略

return caseValue =https://www.cnblogs.com/lindexi/archive/2022/09/29/= 0x65007500720074

干掉這些變數,連成一句代碼,最終的代碼如下

        internal static bool IsTrueStringIgnoreCase(ReadOnlySpan<char> value) =>
            value.Length == 4 &&
            (BinaryPrimitives.ReadUInt64LittleEndian(MemoryMarshal.AsBytes(value)) | 0x0020002000200020) == 0x65007500720074; // "true" as a ulong, each char |'d with 0x0020 for case-insensitivity

以上代碼的 => 是替換原本的方法體的簡寫代碼,原本的代碼里面,方法是使用 {} 組織方法體代碼,在新 C# 里面,如果方法體的代碼只有一句話,那就和辣么大一樣,用箭頭的方式即可,如此可以讓代碼更加簡短,同時方便淘汰那些落后的開發者

同理對 "False" 字串也進行相同的處理,嗯,對于我這個學渣來說,最怕看到“同理”這兩個字,因為我拿出腳趾都算不出來,好像 "False" 字串有五個字符吧,大家算算看

那既然有五個字符,這就意味著不能整個字串轉換為 UInt64 了,畢竟 5 x 16 > 64 了,那咋辦呢?沒關系,先取 "False" 字串前面的 "Fals" 字符出來,按照上文的方式進行比較,最后再比較 e 這個字符好了

       internal static bool IsFalseStringIgnoreCase(ReadOnlySpan<char> value) =>
            value.Length == 5 &&
            (((BinaryPrimitives.ReadUInt64LittleEndian(MemoryMarshal.AsBytes(value)) | 0x0020002000200020) == 0x73006C00610066) & // "fals" as a ulong, each char |'d with 0x0020 for case-insensitivity
            ((value[4] | 0x20) == 'e'));

這能提升多少呢?非常多,大佬的基準測驗如下,可以看到在 .NET 7 的耗時,接近是 .NET 6 的三分之一,優化特別大

Method Runtime Mean Ratio
ParseTrue .NET 6.0 7.347 ns 1.00
ParseTrue .NET 7.0 2.327 ns 0.32

在從字串轉換,可以使用整數對比和轉換的方法提升性能,那轉換為字串呢?其實也相同,也可以使用相同的方法,嗯,又是同理,同理,在將布爾轉換為字串時,可以通過寫入整數的方式提升性能

例如將 true 寫為 "True" 字串,原本的寫入采用的是如下的方法

public bool TryFormat(Span<char> destination, out int charsWritten)
{
    // 這里的 m_value 就是實際存盤的欄位的值,表示當前的布林值
    // 這里傳入的 destination 則是一段需要被寫入的字符內容,咱這個函式就是需要在
    // 傳入的 destination 里將布林值作為 True 或 False 字串寫入
    // 寫入成功回傳 true 的值,且記錄 charsWritten 說明寫入了多少個字符了
    ///寫入失敗,回傳 false 的值
    if (m_value)
    {
    	// 在這里需要寫入 "True" 字串到 destination 里
        if ((uint)destination.Length > 3) // 如果長度都還沒達到能寫入 "True" 字串的最小所需空間,那就不干活了 
        {
            destination[0] = 'T';
            destination[1] = 'r';
            destination[2] = 'u';
            destination[3] = 'e';
            charsWritten = 4;
            return true;
        }
    }
}

上面的代碼也看到起來非常方便理解,在了解了可以使用整數的方式提升性能之后,試試換成使用整數賦值的方式,更改之后的代碼如下

public bool TryFormat(Span<char> destination, out int charsWritten)
{
    // 這里的 m_value 就是實際存盤的欄位的值,表示當前的布林值
    // 這里傳入的 destination 則是一段需要被寫入的字符內容,咱這個函式就是需要在
    // 傳入的 destination 里將布林值作為 True 或 False 字串寫入
    // 寫入成功回傳 true 的值,且記錄 charsWritten 說明寫入了多少個字符了
    ///寫入失敗,回傳 false 的值
    if (m_value)
    {
    	// 在這里需要寫入 "True" 字串到 destination 里
        if ((uint)destination.Length > 3) // 如果長度都還沒達到能寫入 "True" 字串的最小所需空間,那就不干活了 
        {
            // destination[0] = 'T';
            // destination[1] = 'r';
            // destination[2] = 'u';
            // destination[3] = 'e';
            BinaryPrimitives.WriteUInt64LittleEndian(MemoryMarshal.AsBytes(destination), 0x65007500720054); // "True"
            charsWritten = 4;
            return true;
        }
    }
}

先使用 MemoryMarshal.AsBytes 方法將要寫入的 destination 當成 byte 二進制,接著使用 BinaryPrimitives.WriteUInt64LittleEndian 將 "True" 字串對應的整數寫入到二進制里面去,如此即可進行快速的完成寫入字串

同理,也對 "False" 字串進行寫入

                    BinaryPrimitives.WriteUInt64LittleEndian(MemoryMarshal.AsBytes(destination), 0x73006C00610046); // "Fals"
                    destination[4] = 'e';
                    charsWritten = 5;

如此寫入的性能提升也是很多的,大佬的基準測驗里面,對比 dotnet 6 的版本,耗時比例是之前的 0.66 倍

Method Runtime Mean Ratio
FormatTrue .NET 6.0 3.030 ns 1.00
FormatTrue .NET 7.0 1.997 ns 0.66

在底層里面,對布爾這樣的型別進行優化是非常有必要的,可以極大的提升整個上層應用的性能,因為如此底層的邏輯,在很多角落里,無論是否被程式員所關注到,都會被默默的很多次的被呼叫

既然在性能優化的角度上,通過使用整數替換逐個字符的方式,可以提升一些性能,既然學到了,那肯定要用上來呀,我在檔案下載庫的斷點續傳保存檔案里面,就嘗試使用此思想撰寫一點代碼,代碼請看 https://github.com/dotnet-campus/dotnetCampus.FileDownloader/blob/aa99be3a6c9efe5bc590111a5a4b95085061d622/src/dotnetCampus.FileDownloader/Utils/BreakpointResumptionTransmissions/BreakpointResumptionTransmissionRecordFileFormatter.cs#L128-L136

原本我是期望寫入的是 DCFBPRTI 的 ASCII 編碼的字串的,現在更換為使用一個 long 代替,如下面代碼

    private static long GetHeader()
    {
        // 檔案頭是 dotnet campus File Downloader BreakPointResumptionTransmissionInfo 幾個單詞的首個字符 DCFBPRTI 縮寫的 ASCII 值
        // 剛好將這個 ASCII 的 byte 陣列轉換為一個 long 的值
        //var headerByteList = System.Text.Encoding.ASCII.GetBytes("DCFBPRTI");
        // var headerByteList = new byte[] { 68, 67, 70, 66, 80, 82, 84, 73 };
        //return BitConverter.ToInt64(headerByteList)
        return 5283938767475196740;
    }

以上的代碼只是因為我學到了這個方式進行優化,強行想試試而已,不代表著在業務代碼里面一定要使用此方式哦

其實在撰寫代碼的時候,以可讀性為第一,除非遇到的模塊是屬于性能敏感的,但愿閱讀本文不會帶壞一些新手開發者,讓新手開發者想著在任何的地方強行使用寫整數代替可讀性比較高的字串處理方法

博客園博客只做備份,博客發布就不再更新,如果想看最新博客,請到 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/509803.html

標籤:.NET技术

上一篇:支持 dotnet 6 的 dnSpy 神器版本

下一篇:使用 Uno Islands 在現有 WPF 里面嵌入 Uno 框架

標籤雲
其他(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