بهینهسازی تخصیص حافظه با متد جدید StringBuilder.MoveChunks در NET 11.
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۴/۰۳ ۰۹:۵۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
StringBuilder برای مدیریت و اتصال رشتههاست. این کلاس با جلوگیری از ایجاد رشتههای میانی (Intermediate Strings)، فشار روی مکانیسم مدیریت حافظه و به ویژه کامپایلر زباله (Garbage Collector) را به شکل چشمگیری کاهش میدهد. با این حال، حتی در هنگام استفاده از StringBuilder نیز فراخوانی متد ToString() در پایان کار، منجر به تخصیص حافظه (Allocation) برای یک رشتهی نهایی بزرگ میشود.StringBuilder.MoveChunks() معرفی کردهاست که هدف آن، حل این چالش تخصیص حافظه در سناریوهای خاص، به ویژه در ابزارهایی مانند کامپایلر Roslyn و سورس ژنراتورها (Source Generators) است. در این مطلب، به بررسی ساختار داخلی StringBuilder، نحوه عملکرد متد MoveChunks، پیادهسازی آن و در نهایت فلسفه وجودی این API جدید میپردازیم.MoveChunks، ابتدا باید نگاهی به معماری داخلی StringBuilder داشته باشیم. برخلاف تصور رایج، StringBuilder یک آرایهی یکپارچه و بزرگ از کاراکترها نیست که به طور مداوم تغییر سایز دهد؛ بلکه از یک ساختار لیست پیوندی ناپیوسته از آرایههای کاراکتری (char[]) تشکیل شدهاست که در اصطلاح فنی به آنها تکهها (Chunks) گفته میشود.Append فراخوانی میشود، دادهها در چانک فعلی نوشته میشوند. اگر ظرفیت چانک جاری تکمیل شود، یک چانک جدید تخصیص یافته و چانک قبلی به عنوان m_ChunkPrevious به آن متصل میشود. فرآیند تبدیل این ساختار به یک رشته منسجم، تنها زمانی رخ میدهد که متد ToString() فراخوانی شود؛ فرآیندی که مستلزم کپی کردن تمام این تکهها در یک بلوک حافظه پیوسته و جدید است.StringBuilder.MoveChunks یک متد استاتیک است که نمونهی موجود از StringBuilder را دریافت کرده، محتوای داخلی آن را به یک نمونه جدید منتقل میکند و نمونه اولیه را به وضعیت اولیه (Reset) برمیگرداند.// شبیهسازی مفهومی عملکرد MoveChunks var snapshot = new StringBuilder(source.ToString()); source.Clear();
Clear() وجود دارد؟Clear() را روی یک StringBuilder فراخوانی میکنید، آرایههای کاراکتری داخلی (Chunks) آزاد نمیشوند؛ بلکه فقط شاخصها و طول رشته (Length) صفر میشوند تا آرایههای موجود برای استفاده مجدد (Reuse) باقی بمانند. اما در MoveChunks، تمام چانکهای داخلی به نمونهی جدید منتقل شده و شیء اولیه کاملاً خالی از هرگونه چانک حافظه میشود.var source = new StringBuilder();
source.AppendLine("Hello World");
// انتقال چانکها به نمونه جدید
StringBuilder snapshot = StringBuilder.MoveChunks(source);
Console.WriteLine(snapshot.ToString()); // خروجی: "Hello World"
Console.WriteLine(source.ToString()); // خروجی: "" (رشته خالی)
Console.WriteLine(source.Length); // خروجی: 0
// شیء اولیه مجدداً قابل استفاده است
source.Append("Ready for reuse");MoveChunks در داتنت ۱۱ بسیار ساده و در عین حال هوشمندانه پیادهسازی شدهاست. این متد به جای کپی کردن المانهای آرایه، صرفاً مراجع (References) و فیلدهای مقداری (Value Types) را جابهجا میکند:namespace System.Text;
public sealed partial class StringBuilder
{
public static StringBuilder MoveChunks(StringBuilder source)
{
ArgumentNullException.ThrowIfNull(source);
// ساخت یک نمونه جدید با استفاده از سازنده اختصاصی
StringBuilder destination = new StringBuilder(source);
// ریست کردن تمام فیلدهای داخلی شیء مبدا به وضعیت اولیه
source.m_ChunkChars = [];
source.m_ChunkPrevious = null;
source.m_ChunkLength = 0;
source.m_ChunkOffset = 0;
return destination;
}
// Move Constructor (سازنده انتقالی)
private StringBuilder(StringBuilder from)
{
// کپی کردن مراجع آرایهها و مقادیر بدون هیچگونه تخصیص حافظه اضافی
m_ChunkLength = from.m_ChunkLength;
m_ChunkOffset = from.m_ChunkOffset;
m_ChunkChars = from.m_ChunkChars;
m_ChunkPrevious = from.m_ChunkPrevious;
m_MaxCapacity = from.m_MaxCapacity;
// بررسی صحت ساختار در حالت Debug
AssertInvariants();
}
}$O(1)$ است؛ چرا که هیچ حلقهای برای کپی کاراکترها وجود ندارد و صرفاً اشارهگرهای آرایهها منتقل میشوند.source = new()) پیادهسازی کنند. اما انگیزه اصلی معرفی این متد، رفع یک چالش جدی در Source Generators و کامپایلر Roslyn است.StringBuilder وابسته هستند. در پلتفرم Roslyn، برای ساخت ساختار متنی کد از کلاس انتزاعی SourceText استفاده میشود که متدهای کارخانهای (Factory Methods) آن به شرح زیر است:namespace Microsoft.CodeAnalysis.Text;
public abstract class SourceText
{
public static SourceText From(string text, Encoding? encoding = null, ...);
public static SourceText From(TextReader reader, int length, ...);
public static SourceText From(Stream stream, ...);
public static SourceText From(byte[] buffer, ...);
}StringBuilder به SourceText، کامپایلر ناچار به فراخوانی ToString() بود که منجر به ایجاد یک رشته غولپیکر و تخصیص حافظه سنگین در زمان کامپایل میشد. پیشنهاد شد که اورلود جدیدی به شکل زیر اضافه شود:public static SourceText From(StringBuilder stringBuilder, ...);
StringBuilder به Roslyn، محتوای آن را در نخ (Thread) دیگری تغییر میداد، ساختار کدهای تولید شده توسط کامپایلر دچار از هم گسیختگی میشد. Roslyn برای امنیت خود باید یک کپی از دادهها میگرفت که این کار مزیت کارایی را از بین میبرد.MoveChunks نقش نجاتدهنده را ایفا میکند. با این متد، Roslyn محتوای داخلی StringBuilder را به مالکیت خود درآورده و شیء در اختیار کاربر را کاملاً خالی میکند. در نتیجه، کاربر با وجود داشتن مرجع قدیمی، دیگر نمیتواند کدی که در اختیار Roslyn قرار گرفته را دستکاری و ویرایش کند.netstandard2.0 هستند. از آنجا که MoveChunks یک قابلیت اختصاصی در داتنت ۱۱ است، استفاده مستقیم از آن در ژنراتورهای عمومی فعلاً با محدودیت مواجه است. با این حال، تیم داتنت در حال بررسی مکانیزمهایی است تا بتوان از طریق آنالایزرها، کدهایی مانند SourceText.From(sb.ToString()) را شناسایی کرده و آنها را به متدهای بهینهتری مانند SourceText.DrainFrom(sb) هدایت کند.نکته:
اگر در معماری سیستم خود از الگوهایی مانند اشیای اشتراکی مکرر (Object Pooling) برایStringBuilderاستفاده میکنید، توجه داشته باشید که استفاده ازMoveChunksباعث میشود آرایههای داخلی شیء اولیه آزاد شوند. بنابراین در استفادههای بعدی، آرایههای جدیدی تخصیص خواهند یافت. این متد باید صرفاً در گلوگاههایی استفاده شود که قصد دارید تخصیص حافظه حاصل ازToString()روی رشتههای بزرگ را به صفر برسانید.
StringBuilder.MoveChunks یک ابزار پیشرفته و کمهزینه برای جابهجایی مالکیت دادههای متنی بدون کپی کردن آنها در حافظه است. اگرچه این قابلیت در ابتدا با هدف بهینهسازی داخلی کامپایلر Roslyn و سورس ژنراتورها طراحی شدهاست، اما در معماری باز و توسعهپذیر برنامههای بزرگ داتنت که با حجم وسیعی از دادههای متنی سروکار دارند، میتواند به عنوان یک الگوی کارآمد برای طراحی APIهای سریعتر و بدون نیاز به Allocation (Zero-allocation APIs) مورد استفاده قرار گیرد.