提高C# StringBuilder操作性能優(yōu)化的方法
本文探討使用C# StringBuilder 的最佳實踐,用于減少內(nèi)存分配,提高字符串操作的性能。
在 .NET 中,String 對象是不可改變的。每次使用 System.String 類中的方法之一時,都要在內(nèi)存中創(chuàng)建一個新的字符串對象,這就需要為該新對象分配新的空間。
在需要對字符串執(zhí)行重復修改的情況下,與創(chuàng)建新的 String 對象相關的系統(tǒng)開銷可能會非常昂貴。如果要修改字符串而不創(chuàng)建新的對象,則可以使用 System.Text.StringBuilder 類。例如,當在一個循環(huán)中將許多字符串連接在一起時,使用 StringBuilder 類可以提升性能。
BenchmarkDotNet是一款強力的.NET性能基準測試庫,為每個被測試的方法提供了孤立的環(huán)境。使用BenchmarkDotnet, 程序員可以很容易的編寫各種性能測試方法,并可以避免許多常見的坑。
本篇文章中,我們將利用 BenchmarkDotNet 為我們的 StringBuilder 操作進行基準測試。
要使用本篇文章提供的代碼示例,你的系統(tǒng)中應該安裝有 Visual Studio 2019 或者以上版本。
1. 在Visual Studio中創(chuàng)建一個控制臺應用程序項目
首先讓我們在 Visual Studio中 創(chuàng)建一個 .NET Core 控制臺應用程序項目。假設你的系統(tǒng)中已經(jīng)安裝了 Visual Studio 2019,請按照下面的步驟創(chuàng)建一個新的 .NET Core 控制臺應用程序項目。
- 1. 啟動 Visual Studio IDE。
- 2. 點擊 "創(chuàng)建新項目"。
- 3. 在 "創(chuàng)建新項目 "窗口中,從顯示的模板列表中選擇 "控制臺應用程序(.NET核心)"。
- 4. 點擊 "下一步"。
- 5. 在接下來顯示的 "配置你的新項目 "窗口中,指定新項目的名稱和位置。
- 6. 點擊創(chuàng)建。
這將在 Visual Studio 2019 中創(chuàng)建一個新的 .NET Core 控制臺應用程序項目。我們將在本文的后續(xù)章節(jié)中使用這個項目來處理 StringBuilder。
2. 安裝 BenchmarkDotNet NuGet包
要使用 BenchmarkDotNet,你必須安裝 BenchmarkDotNet 軟件包。你可以通過 Visual Studio 2019 IDE 內(nèi)的 NuGet 軟件包管理器,或在 NuGet 軟件包管理器控制臺執(zhí)行以下命令來完成。
Install-Package BenchmarkDotNet
3. 使用 StringBuilderCache 來減少分配
StringBuilderCache 是一個內(nèi)部類,在 .NET 和 .NET Core 中可用。每當你需要創(chuàng)建多個 StringBuilder 的實例時,你可以使用 StringBuilderCache 來大大減少分配的成本。
StringBuilderCache 的工作原理是緩存一個 StringBuilder 實例,然后在需要一個新的 StringBuilder 實例時重新使用它。這減少了分配,因為你只需要在內(nèi)存中擁有一個 StringBuilder 實例。
讓我們用一些代碼來說明這一點。在 Program.cs 文件中創(chuàng)建一個名為 StringBuilderBenchmarkDemo 的類。創(chuàng)建一個名為 AppendStringUsingStringBuilder 的方法,代碼如下。
public string AppendStringUsingStringBuilder() { var stringBuilder = new StringBuilder(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return stringBuilder.ToString(); }
上面的代碼片段顯示了如何使用 StringBuilder 對象來追加字符串。接下來創(chuàng)建一個名為 AppendStringUsingStringBuilderCache 的方法,代碼如下。
public string AppendStringUsingStringBuilderCache() { var stringBuilder = StringBuilderCache.Acquire(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return StringBuilderCache.GetStringAndRelease(stringBuilder); }
上面的代碼片段說明了如何使用 StringBuilderCache 類的 Acquire 方法創(chuàng)建一個 StringBuilder 實例,然后用它來追加字符串。
下面是 StringBuilderBenchmarkDemo 類的完整源代碼供你參考。
[MemoryDiagnoser] public class StringBuilderBenchmarkDemo { [Benchmark] public string AppendStringUsingStringBuilder() { var stringBuilder = new StringBuilder(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return stringBuilder.ToString(); } [Benchmark] public string AppendStringUsingStringBuilderCache() { var stringBuilder = StringBuilderCache.Acquire(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return StringBuilderCache.GetStringAndRelease(stringBuilder); } }
你現(xiàn)在必須使用 BenchmarkRunner 類來指定初始起點。這是一種通知 BenchmarkDotNet 在指定的類上運行基準的方式。
用以下代碼替換 Main 方法的默認源代碼。
static void Main(string[] args) { var summary = BenchmarkRunner.Run<StringBuilderBenchmarkDemo>(); }
現(xiàn)在在 Release 模式下編譯你的項目,并在命令行使用以下命令運行基準測試。
dotnet run -p StringBuilderPerfDemo.csproj -c Release
下面說明了兩種方法的性能差異。
正如你所看到的,使用 StringBuilderCache 追加字符串要快得多,需要的分配也少。
4. 使用 StringBuilder.AppendJoin 而不是 String.Join
String 對象是不可變的,所以修改一個 String 對象需要創(chuàng)建一個新的 String 對象。因此,在連接字符串時,你應該使用 StringBuilder.AppendJoin 方法,而不是String.Join,以減少分配,提高性能。
下面的代碼列表說明了如何使用 String.Join 和 StringBuilder.AppendJoin 方法來組裝一個長字符串。
[Benchmark] public string UsingStringJoin() { var list = new List < string > { "A", "B", "C", "D", "E" }; var stringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++) { stringBuilder.Append(string.Join(' ', list)); } return stringBuilder.ToString(); } [Benchmark] public string UsingAppendJoin() { var list = new List < string > { "A", "B", "C", "D", "E" }; var stringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++) { stringBuilder.AppendJoin(' ', list); } return stringBuilder.ToString(); }
下圖顯示了這兩種方法的基準測試結(jié)果。
請注意,對于這個操作,這兩種方法的速度很接近,但 StringBuilder.AppendJoin 使用的內(nèi)存明顯較少。
5. 使用 StringBuilder 追加單個字符
注意,在使用 StringBuilder 時,如果需要追加單個字符,應該使用 Append(char) 而不是 Append(String)。
請考慮以下兩個方法。
[Benchmark] public string AppendStringUsingString() { var stringBuilder = new StringBuilder(); for (int i = 0; i < 1000; i++) { stringBuilder.Append("a"); stringBuilder.Append("b"); stringBuilder.Append("c"); } return stringBuilder.ToString(); } [Benchmark] public string AppendStringUsingChar() { var stringBuilder = new StringBuilder(); for (int i = 0; i < 1000; i++) { stringBuilder.Append('a'); stringBuilder.Append('b'); stringBuilder.Append('c'); } return stringBuilder.ToString(); }
從名字中就可以看出,AppendStringUsingString 方法說明了如何使用一個字符串作為 Append 方法的參數(shù)來追加字符串。
AppendStringUsingChar 方法說明了你如何在 Append 方法中使用字符來追加字符。
下圖顯示了這兩種方法的基準測試結(jié)果。
6. 其他 StringBuilder 優(yōu)化方法
StringBuilder 允許你設置容量以提高性能。如果你知道你要創(chuàng)建的字符串的大小,你可以相應地設置初始容量以大大減少內(nèi)存分配。
你還可以通過使用一個可重復使用的 StringBuilder 對象池來避免分配來提高 StringBuilder 的性能。
最后,請注意,由于 StringBuilderCache是一個內(nèi)部類,你需要將源代碼粘貼到你的項目中才能使用它。回顧一下,在C#中你只能在同一個程序集或庫中使用一個內(nèi)部類。
因此,我們的程序文件不能僅僅通過引用 StringBuilderCache 所在的庫來訪問 StringBuilderCache 類。
這就是為什么我們把 StringBuilderCache 類的源代碼復制到我們的程序文件中,也就是Program.cs文件。
參考資料:
1. C#教程
2. C#編程技術
3. 編程寶庫
ASP.NET提高StringBuilder類操作性能就向你介紹到這里,希望對你有所幫助。
相關文章
詳解C# 泛型中的數(shù)據(jù)類型判定與轉(zhuǎn)換
這篇文章主要介紹了C# 泛型中的數(shù)據(jù)類型判定與轉(zhuǎn)換,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下2020-07-07