از بهترین روش ها برای استفاده از StringBuilder برای کاهش تخصیص حافظه و بهبود عملکرد عملیات رشته خود استفاده کنید.
رشته ها انواع غیرقابل تغییر در دات نت هستند. هر زمان که یک شی String را در دات نت تغییر می دهید، یک شی String جدید در حافظه ایجاد می شود تا داده های جدید را نگه دارد. در مقابل، یک شی StringBuilder یک رشته قابل تغییر از کاراکترها را نشان می دهد و با افزایش اندازه رشته، تخصیص حافظه خود را به صورت پویا گسترش می دهد.
کلاسهای String و StringBuilder دو کلاس محبوب هستند که هنگام کار با رشتهها در NET Framework و NET Core اغلب از آنها استفاده خواهید کرد. با این حال، هر کدام مزایا و معایب خود را دارند.
در پست قبلی اینجا، درباره نحوه مقایسه این دو کلاس و اینکه چه زمانی باید یکی به جای دیگری استفاده شود بحث کردم. در این مقاله به نحوه بهبود عملکرد StringBuilder در سی شارپ می پردازم.
BenchmarkDotNet یک کتابخانه سبک وزن و منبع باز برای محک زدن کد NET است. BenchmarkDotNet میتواند روشهای شما را به معیار تبدیل کند، آن روشها را ردیابی کند، و سپس بینشهایی در مورد دادههای عملکرد گرفتهشده ارائه دهد. ما از BenchmarkDotNet برای محک زدن عملیات StringBuilder خود در این پست استفاده خواهیم کرد.
برای کار با نمونه کدهای ارائه شده در این مقاله، باید Visual Studio 2019 را در سیستم خود نصب کنید. اگر قبلاً نسخهای ندارید، میتوانید Visual Studio 2019 را از اینجا بارگیری کنید.
یک پروژه برنامه کاربردی کنسول در ویژوال استودیو ایجاد کنید
ابتدا اجازه دهید یک پروژه برنامه کاربردی کنسول NET Core در ویژوال استودیو ایجاد کنیم. با فرض اینکه Visual Studio 2019 در سیستم شما نصب شده است، مراحل ذکر شده در زیر را برای ایجاد یک پروژه برنامه کاربردی کنسول NET Core جدید دنبال کنید.
- Visual Studio IDE را راه اندازی کنید.
- روی “ایجاد پروژه جدید” کلیک کنید.
- در پنجره “ایجاد پروژه جدید”، “Console App (.NET Core)” را از لیست الگوهای نمایش داده شده انتخاب کنید.
- بعدی را کلیک کنید.
- در پنجره “پیکربندی پروژه جدید خود” که در ادامه نشان داده شده است، نام و مکان پروژه جدید را مشخص کنید.
- روی ایجاد کلیک کنید.
این یک پروژه برنامه کاربردی کنسول NET Core جدید در Visual Studio 2019 ایجاد میکند. ما از این پروژه برای کار با StringBuilder در بخشهای بعدی این مقاله استفاده خواهیم کرد.
بسته BenchmarkDotNet NuGet را نصب کنید
برای کار با BenchmarkDotNet باید بسته BenchmarkDotNet را نصب کنید. می توانید این کار را از طریق NuGet Package Manager در داخل Visual Studio 2019 IDE یا با اجرای دستور زیر در کنسول NuGet Package Manager انجام دهید:
Install-Package BenchmarkDotNet
از StringBuilderCache برای کاهش تخصیص ها استفاده کنید
StringBuilderCache یک کلاس داخلی است که در .NET و .NET Core موجود است. هر زمان که نیاز به ایجاد چندین نمونه از StringBuilder داشتید، می توانید از StringBuilderCache برای کاهش قابل توجه هزینه تخصیص ها استفاده کنید.
StringBuilderCache با ذخیره سازی یک نمونه StringBuilder و استفاده مجدد از آن در صورت نیاز به یک نمونه StringBuilder جدید کار می کند. این امر تخصیص ها را کاهش می دهد زیرا شما باید فقط یک نمونه StringBuilder در حافظه داشته باشید.
اجازه دهید این را با چند کد توضیح دهیم. یک کلاس به نام StringBuilderBenchmarkDemo در فایل Program.cs ایجاد کنید. یک متد به نام AppendStringUsingStringBuilder با کد زیر ایجاد کنید:
public string AppendStringUsingStringBuilder()
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("First String");
stringBuilder.Append("Second String");
stringBuilder.Append("Third String");
return stringBuilder.ToString();
}
قطعه کد بالا نشان می دهد که چگونه می توانید از یک شی StringBuilder برای اضافه کردن رشته ها استفاده کنید. سپس متدی به نام AppendStringUsingStringBuilderCache با کد زیر ایجاد کنید:
public string AppendStringUsingStringBuilderCache()
{
var stringBuilder = StringBuilderCache.Acquire();
stringBuilder.Append("First String");
stringBuilder.Append("Second String");
stringBuilder.Append("Third String");
return StringBuilderCache.GetStringAndRelease(stringBuilder);
}
قطعه کد بالا نشان می دهد که چگونه می توانید یک نمونه StringBuilder را با استفاده از متد Acquire کلاس StringBuilderCache ایجاد کنید و سپس از آن برای اضافه کردن رشته ها استفاده کنید.
در اینجا کد منبع کامل کلاس 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);
}
}
اکنون باید نقطه شروع اولیه را با استفاده از کلاس BenchmarkRunner مشخص کنید. این روشی برای اطلاع دادن به BenchmarkDotNet برای اجرای معیارها در کلاس مشخص شده است. کد منبع پیش فرض روش Main را با استفاده از کد زیر جایگزین کنید:
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<StringBuilderBenchmarkDemo>();
}
اکنون پروژه خود را در حالت انتشار کامپایل کنید و با استفاده از دستور زیر در خط فرمان، بنچمارک را اجرا کنید:
dotnet run -p StringBuilderPerfDemo.csproj -c Release
شکل ۱ زیر تفاوت عملکرد دو روش را نشان می دهد.
شکل ۱. مقایسه عملکرد StringBuilder با و بدون StringBuilderCache.
همانطور که میبینید، اضافه کردن رشتهها با استفاده از StringBuilderCache بسیار سریعتر است و به تخصیصهای کمتری نیاز دارد.
از StringBuilder.AppendJoin به جای String.Join استفاده کنید
به یاد بیاورید که اشیاء String تغییرناپذیر هستند، بنابراین اصلاح یک شی String نیاز به ایجاد یک شی 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();
}
شکل ۲ زیر نتایج محک این دو روش را نشان می دهد. توجه داشته باشید که برای این عملیات دو روش از نظر سرعت نزدیک بودند، اما StringBuilder.AppendJoin از حافظه بسیار کمتری استفاده میکرد.
شکل ۲. مقایسه String.Join و StringBuilder.AppendJoin.
یک کاراکتر را با استفاده از 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 اضافه کنید. روش AppendStringUsingChar نشان می دهد که چگونه می توانید از کاراکترها در متد Append برای اضافه کردن کاراکترها استفاده کنید. شکل ۳ زیر نتیجه محک زدن دو روش را نشان می دهد.
شکل ۳. مقایسه عملیات Append(char) و Append(String) هنگام استفاده از StringBuilder.
سایر بهینه سازی های StringBuilder
StringBuilder به شما امکان می دهد ظرفیت را برای افزایش عملکرد تنظیم کنید. اگر اندازه رشته ای را که می خواهید ایجاد کنید می دانید، می توانید ظرفیت اولیه را بر اساس آن تنظیم کنید تا تخصیص حافظه را به میزان قابل توجهی کاهش دهید.
برای جلوگیری از تخصیص، میتوانید عملکرد StringBuilder را با استفاده از یک مجموعه قابل استفاده مجدد از اشیاء StringBuilder بهبود بخشید. این نکات قبلاً در مقاله قبلی اینجا بحث شده است.
در نهایت، توجه داشته باشید که چون StringBuilderCache یک کلاس داخلی است، باید کد منبع را وارد پروژه خود کنید تا از آن استفاده کنید. به یاد داشته باشید که می توانید از یک کلاس داخلی در سی شارپ فقط در همان اسمبلی یا کتابخانه استفاده کنید. از این رو فایل برنامه ما نمی تواند صرفاً با ارجاع به کتابخانه ای که StringBuilderCache در آن موجود است به کلاس StringBuilderCache دسترسی پیدا کند.
به همین دلیل است که ما کد منبع کلاس StringBuilderCache را در فایل برنامه خود، یعنی فایل Program.cs کپی کرده ایم.
پست های مرتبط
نحوه بهبود عملکرد StringBuilder در سی شارپ
نحوه بهبود عملکرد StringBuilder در سی شارپ
نحوه بهبود عملکرد StringBuilder در سی شارپ