مدیریت حافظه به صورت خودکار، که از طریق جمعآورندههای زباله (Garbage Collectors) انجام میشود، یکی از ارکان اصلی بسیاری از زبانهای برنامهنویسی مدرن از جمله سیشارپ (.NET)، جاوا، گو، روبی، جاوااسکریپت و پیاچپی به شمار میرود. این مکانیزم، با حذف نیاز به مدیریت دستی حافظه، فرآیند توسعه را تسهیل کرده و از بروز خطاهایی نظیر نشت حافظه (Memory Leak) جلوگیری میکند. با این حال، عملکرد جمعآورندههای زباله، به ویژه در برنامههای کاربردی با مقیاس بزرگ و توان عملیاتی بالا، میتواند تأثیر بسزایی بر کارایی و پاسخگویی سیستم داشته باشد. مکثهای طولانی ناشی از فرآیند جمعآوری زباله، میتواند تجربه کاربری را مختل کرده و در سیستمهای حساس به تأخیر، مشکلساز شود. در این میان، اکوسیستمهای دیگری مانند جاوا و به خصوص گو، با ارائه نوآوریهایی در زمینه جمعآوری زباله، توجه بسیاری را به خود جلب کردهاند. ظهور یک جمعآورنده زباله آزمایشی جدید به نام Satori در محیط زمان اجرای داتنت (.NET runtime)، بارقهای از امید را در میان توسعهدهندگان داتنت برای دستیابی به کارایی بالاتر و تأخیرهای به مراتب کمتر ایجاد کرده است.
درک مفهوم جمعآوری زباله و اهمیت آن
هنگامیکه از یک جمعآورنده زباله استفاده میکنید، در واقع کنترل نحوه مدیریت حافظه و زمان اجرای برنامه را تا حدی به آن واگذار میکنید. اکثر جمعآورندههای زباله برای تعیین اینکه کدام اشیاء در حافظه هنوز در حال استفاده هستند و کدامیک دیگر نیازی ندارند، نیاز به متوقف کردن موقت اجرای برنامه در فرآیندی به نام "توقف دنیا" (Stop the World) دارند. پس از شناسایی اشیاء غیرضروری، حافظه آنها آزاد شده و ممکن است اشیاء باقیمانده برای بهبود کارایی در مناطق پیوسته حافظه سازماندهی مجدد شوند.
همانطور که تصور میکنید، این توقفهای ناگهانی میتوانند عملکرد برنامه را مختل کنند، به خصوص اگر مدت زمان این توقفها طولانی باشد. به همین دلیل است که زبانهای سطح پایینتر مانند راست (Rust)، سی/سی++ و زیگ (Zig) که نیاز به کنترل دقیق بر رفتار برنامه دارند، معمولاً از جمعآوری زباله استفاده نمیکنند و در عوض، مدیریت حافظه به صورت دستی توسط توسعهدهنده انجام میشود. اگرچه مدیریت دستی حافظه انعطافپذیری بیشتری ارائه میدهد، اما مستعد خطاهایی مانند فراموش کردن آزادسازی حافظه و در نتیجه نشت حافظه است.
زبانهای دارای جمعآورنده زباله، تا حد زیادی از مشکل نشت حافظه مصون هستند (اگرچه انواع دیگر نشت منابع همچنان ممکن است رخ دهد). این امر، یکی از دلایل اصلی محبوبیت این نوع زبانها است، زیرا توسعه را ایمنتر و سادهتر میکند. با این حال، در برنامههای کاربردی بزرگ و با حجم کاری بالا، رفتار جمعآورنده زباله میتواند به یک عامل محدود کننده تبدیل شود.
تحولات تاریخی جمعآورنده زباله در داتنت
جمعآورنده زباله داتنت در طول سالها تکامل یافته و بهبودهای چشمگیری را تجربه کرده است. در ابتدا، "جمعآورنده زباله ایستگاه کاری" (Workstation GC) برای برنامههای کاربردی رابط کاربری دسکتاپ طراحی شد. این جمعآورنده عمدتاً تکرشتهای بوده و از یک پشته حافظه واحد برای ذخیره اشیاء مدیریتشده استفاده میکند.
با افزایش تقاضای برنامههای داتنت، "جمعآورنده زباله سرور" (Server GC) با هدف به حداکثر رساندن توان عملیاتی برنامه، حتی به قیمت افزایش زمانهای مکث، معرفی شد. جمعآورنده زباله سرور معمولاً با انجام تعداد کمتری جمعآوری بزرگتر، کارآمدتر عمل میکند. تفاوت اصلی آن با جمعآورنده زباله ایستگاه کاری، استفاده از چندین پشته حافظه (معمولاً یک پشته به ازای هر هسته پردازنده) برای ذخیره اشیاء بود. این امر، مدیریت و تخصیص حافظه را در سیستمهای بزرگ با تعداد هستههای زیاد، تسهیل میکند. همچنین، جمعآورنده زباله سرور به طور پیشفرض از حافظه بیشتری استفاده میکند تا با اجازه دادن به تجمع بیشتر اشیاء، تعداد دفعات جمعآوری را کاهش دهد.
با بلوغ بیشتر داتنت، ویژگیهای پیشرفتهتری مانند جمعآوری زباله همزمان (Concurrent garbage collection) برای هر دو حالت ایستگاه کاری و سرور ارائه شد. این قابلیت به جمعآورنده زباله اجازه میداد تا از چندین رشته برای جمعآوری حافظه استفاده کند و به کاهش زمانهای مکث کمک میکرد. جمعآوری زباله همزمان در نسخههای بعدی داتنت با "جمعآوری زباله پسزمینه" (Background garbage collection) جایگزین شد که مقیاسپذیری جمعآورنده زباله را بهبود بخشید.
نوآوریها و ویژگیهای بیشتری نیز در طول سالها به داتنت اضافه شدهاند. یکی از ویژگیهای مهم اخیر، DATAS نام دارد که با کمی کاهش در توان عملیاتی برنامه، به طور چشمگیری اندازه پشته حافظه را هنگام استفاده از جمعآورنده زباله سرور کاهش میدهد.
در حالی که داتنت به پیشرفت خود ادامه میداد، سایر اکوسیستمها نیز شاهد نوآوریهایی در زمینه جمعآوری زباله بودند. به عنوان مثال، جاوا اکوسیستم پویایی دارد که در آن توسعهدهندگان میتوانند به طور کامل جمعآورنده زباله پیشفرض را با گزینههای دیگر جایگزین کنند. همچنین، زبان برنامهنویسی گو با پیشرفتهای چشمگیر در جمعآورنده زباله خود، توجه بسیاری را به خود جلب کرد. زمانهای مکث بسیار کوتاه (اغلب کمتر از یک میلیثانیه) در گو، باعث حسادت توسعهدهندگان سایر زبانها و مقایسههای فراوان شد.
در مقایسه بین داتنت و گو، نکته مهم این است که اگرچه گو زمانهای مکث کوتاهتری ارائه میداد، داتنت از توان عملیاتی بالاتری برخوردار بود. این یک معاوضه آگاهانه توسط تیم داتنت بود که در طول سالها عملکرد نسبتاً خوبی داشته است. با این حال، مکثهای جمعآوری زباله همچنان میتوانند آزاردهنده باشند، به خصوص با توجه به اینکه سایر جنبههای عملکرد داتنت در 10 سال گذشته به طور چشمگیری بهبود یافتهاند. درخواستهای وب که قبلاً 250 میلیثانیه طول میکشیدند، اکنون ممکن است تنها 10 میلیثانیه طول بکشند و در این شرایط، حتی یک مکث کوتاه جمعآوری زباله نیز بیشتر به چشم میآید.
ظهور Satori: یک جمعآورنده زباله با تأخیر زیر میلیثانیه
در طول 10 تا 15 سال گذشته، هرگاه سؤالی در مورد جمعآورندههای زباله جایگزین برای داتنت مطرح میشد، تیم توسعه داتنت به طور صبورانه توضیح میداد که داتنت از ویژگیهای پیچیدهتری (مانند اشارهگرهای داخلی) پشتیبانی میکند که برخی دیگر از اکوسیستمها (مانند جاوا) فاقد آن هستند. آنها همچنین در مورد معاوضهها توضیح داده و پاسخدهنده را راهنمایی میکردند. به همین دلیل، هنگامی که یک بحث دیگر در مورد یک جمعآورنده زباله بدون مکث در داتنت دیدم، پاسخ استاندارد را ارائه کردم.
اکنون، دو سال به جلو میرویم. این بحث به مدت 8 ماه بدون پاسخ مانده بود که ناگهان، ولادیمیر سادوف (Vladimir Sadov)، مهندس زمان اجرای داتنت، ایدهای را مطرح کرد که یک جمعآورنده زباله میتواند زمانهای مکث را به 2-3 میلیثانیه کاهش دهد، اما این فقط یک نظریه بود. تا اینکه دیگر نبود. ظاهراً ولادیمیر روی یک شاخه آزمایشی از زمان اجرا کار میکرده است که یک جمعآورنده زباله جایگزین (به نام Satori) را ارائه میدهد که از نظر تأخیر مکث، به طور کامل جمعآورندههای زباله سرور و ایستگاه کاری موجود را، حتی با وجود تمام نوآوریهای سالهای اخیر، شکست میدهد.
این خبر برای توسعهدهندگان داتنت که بر روی سیستمهای پردازش مالی با حساسیت بالا به تأخیر کار میکنند، بسیار هیجانانگیز است. ایده دستیابی به هر دو مزیت توان عملیاتی بالا و زمانهای مکث کم، بسیار جذاب است و نتایج اولیه بسیار دلگرمکننده بودهاند.
عملکرد Satori چگونه است؟
اگرچه Satori حدود 15-20٪ کاهش در توان عملیاتی تخصیص حافظه نشان میدهد، بهبود چشمگیر در زمان مکث، آن را در میان جمعآورندههای زباله با کمترین تأخیر در صنعت قرار میدهد. علاوه بر این، Satori اندازه پشته حافظه را به طور قابل توجهی کوچکتر از جمعآورنده زباله سرور نگه میدارد. این امر تأثیر بسزایی بر عملکرد بنچمارکها دارد. به عنوان مثال، در یک بنچمارک مصنوعی 30 ثانیهای، جمعآورنده زباله سرور موجود حدود 2.6 ثانیه از آن زمان (حدود 8٪) را صرف جمعآوری زباله کرد، در حالی که Satori تنها به 156 میلیثانیه (حدود 0.5٪) نیاز داشت. این بدان معناست که زمان واقعی بیشتری برای اجرای کد برنامه در دسترس قرار میگیرد.
جدول زیر، نتایج یک بنچمارک استرس جمعآورنده زباله را نشان میدهد:
| حالت (Mode) | تعداد جمعآوری زباله (GC Count) | درصد زمان جمعآوری زباله (GC Time %) | نرخ تخصیص حافظه (Allocation Rate MB/s) | P50 | p90 | p99 | pMax |
| workstation-batch | 38 | 88.42 | 39.1 | 971.571 | 1015.808 | 1211.597 | 1211.569 |
| workstation-interactive | 36 | 88.04 | 39.19 | 997.785 | 1037.926 | 2351.104 | 2351.104 |
| workstation-lowlatency | 2622 | 95.96 | 46.63 | 0.042 | 0.064 | 421.069 | 1131.315 |
| workstation-sustainedlowlatency | 39 | 88.69 | 37.92 | 985.497 | 1042.841 | 2156.134 | 2156.134 |
| server-batch | 19 | 10.61 | 172.7 | 157.594 | 495.616 | 495.616 | 495.616 |
| server-interactive | 20 | 11.03 | 174.46 | 148.48 | 153.6 | 772.915 | 772.915 |
| server-sustained-lowlatency | 19 | 11.23 | 172.91 | 165.888 | 801.178 | 801.178 | 801.178 |
| server-batch-datas | 78 | 42.55% | 112.94 | 154.522 | 171.622 | 491.52 | 1124.762 |
| server-interactive-datas | 49 | 96.38 | 23.61 | 1073.971 | 1116.57 | 1143.603 | 1143.603 |
| server-sustained-lowlatency-datas | 46 | 96.45 | 22.96 | 1102.643 | 1154.253 | 1171.456 | 1171.456 |
| satori-interactive | 21 | N/A | 144.75 | 0.203 | 31.166 | 27.853 | 27.853 |
| satori-lowlatency | 21 | N/A | 147.62 | 0.143 | 0.192 | 5.491 | 5.491 |
این اعداد به وضوح گویای عملکرد Satori هستند. در تمام بنچمارکهای انجام شده تاکنون، Satori زمانهای مکث زیر میلیثانیه را در صدک 90 و در بسیاری از موارد در صدک 99 ارائه میدهد. حداکثر زمانهای مکث در مقایسه با جمعآورنده زباله سرور موجود، بین 20 تا 100 برابر یا حتی بیشتر در برخی موارد بهبود یافته است. جمعآورنده زباله ایستگاه کاری که برای بارهای کاری با تأخیر کم طراحی شده است، احتمالاً برای رقابت عادلانه نیاز به بازطراحی دارد، زیرا طراحی تکرشتهای آن بسیار کندتر و غیرقابل پیشبینیتر از سایر گزینهها است.
نحوه استفاده از Satori
استفاده از Satori نسبتاً ساده است:
- برنامه خود را با هدف قرار دادن داتنت 8.0 (.NET 8.0) بسازید.
- برنامه خود را در حالت خودکفا (self-contained mode) منتشر کنید:
dotnet publish –self-contained -c Release -o .\pub
- پس از انتشار، دو فایل DLL اصلاح شده را در پوشه انتشار کپی کنید. اکنون میتوانید برنامه خود را بدون هیچ تغییر دیگری اجرا کنید.
- این فایلها برای ویندوز ارائه شدهاند. (توسعه برای لینوکس در حال انجام است.)
- اگر تمایلی به دانلود DLLهای ناشناس از اینترنت ندارید، میتوانید مخزن زمان اجرای داتنت را کلون کرده و دستور
build.cmd clr -c Release را اجرا کنید.
دستورالعملهای اصلی توسط ولادیمیر سادوف ارائه شدهاند.
توصیه میشود Satori را بر روی بارهای کاری خود آزمایش کرده و نتایج را در بحث مربوطه گزارش دهید.
نتیجهگیری
ظهور جمعآورنده زباله آزمایشی Satori، یک پیشرفت قابل توجه در زمینه کارایی برنامههای داتنت محسوب میشود. نتایج اولیه بنچمارکها نشان میدهد که Satori قادر است زمانهای مکث را به طور چشمگیری کاهش داده و در عین حال، توان عملیاتی قابل قبولی را حفظ کند. کاهش قابل توجه اندازه پشته حافظه نیز یکی دیگر از مزایای قابل توجه این جمعآورنده زباله جدید است. اگرچه Satori هنوز در مراحل آزمایشی قرار دارد، پتانسیل آن برای بهبود عملکرد برنامههای کاربردی حساس به تأخیر و با حجم کاری بالا در اکوسیستم داتنت بسیار امیدوارکننده است. توسعهدهندگان داتنت تشویق میشوند تا این جمعآورنده زباله جدید را آزمایش کرده و بازخورد خود را با تیم توسعه داتنت به اشتراک بگذارند تا به اولویتبندی سرمایهگذاری در این پروژه کمک کنند.