هدف این راهنما اینه که به کاربرهای پیشرفته Go کمک کنه هزینههای برنامهشون رو بهتر درک کنن؛ این کار با ارائه بینشی درباره garbage collector زبان Go انجام میشه. همچنین راهنمایی میکنه که کاربرهای Go چطور میتونن از این بینشها برای بهبود بهرهوری منابع برنامههاشون استفاده کنن. این راهنما هیچ دانش قبلیای درباره garbage collection لازم نداره، ولی فرض میکنه که با زبان برنامهنویسی Go آشنایی دارید.
زبان Go خودش مسئول ترتیبدهی ذخیرهسازی مقادیر Go هست؛ در بیشتر موارد، یه توسعهدهنده Go لازم نیست نگران این باشه که این مقادیر کجا ذخیره میشن یا چرا. ولی در عمل، این مقادیر اغلب باید در حافظه فیزیکی کامپیوتر ذخیره بشن و حافظه فیزیکی هم یه منبع محدوده. چون محدوده، حافظه باید با دقت مدیریت و بازیافت بشه تا موقع اجرای یه برنامه Go، از تموم شدنش جلوگیری بشه. تخصیص و بازیافت حافظه در صورت نیاز، وظیفه یه implementation از Go هست.
یه اصطلاح دیگه برای بازیافت خودکار حافظه، garbage collection هست. در سطح کلی، یه garbage collector (یا بهاختصار GC) سیستمی هست که حافظه رو بهنمایندگی از برنامه بازیافت میکنه؛ یعنی تشخیص میده کدوم بخشهای حافظه دیگه لازم نیستن. toolchain استاندارد Go یه runtime library ارائه میده که همراه هر برنامه ارسال میشه و این runtime library شامل یه garbage collector هست.
⚠️ توجه کنید که وجود یه garbage collector به شکلی که این راهنما توصیف میکنه، توسط Go specification تضمین نشده؛ فقط این تضمین شده که ذخیرهسازی زیرین برای مقادیر Go توسط خود زبان مدیریت میشه. این حذف عمدی هست و امکان استفاده از تکنیکهای کاملاً متفاوت مدیریت حافظه رو فراهم میکنه.
پس این راهنما درباره یه implementation مشخص از زبان Go هست و ممکنه برای implementationهای دیگه کاربرد نداشته باشه. بهطور مشخص، راهنمای زیر برای toolchain استاندارد (کامپایلر و ابزارهای gc در Go) هست. Gccgo و Gollvm هر دو از یه implementation خیلی مشابه GC استفاده میکنن، پس خیلی از مفاهیم یکسان کاربرد دارن، ولی جزئیات ممکنه فرق کنه.
💡 علاوه بر این، این یه سند زندهست و بهمرور زمان تغییر میکنه تا بهترین بازتاب از آخرین نسخه Go باشه. این سند در حال حاضر garbage collector رو در نسخه Go 1.19 توصیف میکنه.
مقادیر Go کجا زندگی میکنن؟¶
قبل از اینکه بریم سراغ GC، اول درباره حافظهای حرف بزنیم که نیازی به مدیریت توسط GC نداره.
مثلاً مقادیر Go غیراشارهگر (non-pointer) که در متغیرهای محلی ذخیره شدن، احتمالاً اصلاً توسط Go GC مدیریت نمیشن؛ بهجاش Go ترتیبی میده که حافظه به شکلی تخصیص پیدا کنه که به lexical scopeای که توش ایجاد شده گره بخوره. بهطور کلی، این روش از تکیه بر GC کارآمدتره، چون کامپایلر Go میتونه از پیش تعیین کنه که اون حافظه چه زمانی میتونه آزاد بشه و دستورات ماشینی منتشر کنه که پاکسازیش کنن. معمولاً به این روش تخصیص حافظه برای مقادیر Go، «stack allocation» میگیم، چون فضا روی goroutine stack ذخیره میشه.
مقادیر Go که حافظهشون به این روش قابل تخصیص نیست — چون کامپایلر Go نمیتونه طول عمرش رو تعیین کنه — میگیم به heap escape میکنن. «The heap» رو میتونیم یه محل جامع برای تخصیص حافظه در نظر بگیریم، برای وقتی که مقادیر Go باید جایی قرار بگیرن. عمل تخصیص حافظه روی heap معمولاً «dynamic memory allocation» نامیده میشه، چون هم کامپایلر و هم runtime فرضهای خیلی کمی درباره اینکه این حافظه چطور استفاده میشه و چه زمانی میتونه پاکسازی بشه دارن. اینجاست که یه GC وارد میشه: سیستمی که بهطور خاص تخصیصهای dynamic memory رو شناسایی و پاکسازی میکنه.
دلایل زیادی هست که چرا یه مقدار Go ممکنه نیاز داشته باشه به heap escape کنه. یه دلیل میتونه این باشه که اندازهش بهصورت dynamic تعیین میشه. مثلاً backing array یه slice رو در نظر بگیرید که اندازه اولیهش بهجای یه ثابت، توسط یه متغیر تعیین میشه. توجه کنید که escape به heap باید transitive هم باشه: اگه یه reference به یه مقدار Go در یه مقدار Go دیگه نوشته بشه که قبلاً مشخص شده escape میکنه، اون مقدار هم باید escape کنه.
اینکه یه مقدار Go escape میکنه یا نه، تابعی از contextایه که توش استفاده میشه و الگوریتم escape analysis کامپایلر Go. تلاش برای برشمردن دقیق زمان escape مقادیر، شکننده و سخته: خود الگوریتم نسبتاً پیچیدهست و بین نسخههای Go تغییر میکنه. برای جزئیات بیشتر درباره اینکه چطور بفهمیم کدوم مقادیر escape میکنن و کدوم نه، به بخش حذف heap allocationها مراجعه کنید.
ردیابی Garbage Collection¶
Garbage collection ممکنه به روشهای مختلفی از بازیافت خودکار حافظه اشاره کنه؛ مثلاً reference counting. در context این سند، garbage collection به tracing garbage collection اشاره داره که objectهای در حال استفاده — بهاصطلاح live — رو با دنبال کردن اشارهگرها بهصورت transitive شناسایی میکنه.
بیاید این اصطلاحات رو دقیقتر تعریف کنیم.
-
Object — یه object یه قطعه حافظهست که بهصورت dynamic تخصیص یافته و شامل یک یا چند مقدار Go میشه.
-
Pointer — یه آدرس حافظه که به هر مقداری درون یه object اشاره میکنه. این بهطور طبیعی شامل مقادیر Go از نوع
*Tمیشه، ولی بخشهایی از مقادیر built-in زبان Go رو هم دربر میگیره. stringها، sliceها، channelها، mapها و مقادیر interface همگی شامل آدرسهای حافظهای هستن که GC باید ردیابیشون کنه.
objectها و اشارهگرها به سایر objectها با هم object graph رو تشکیل میدن. برای شناسایی حافظه live، GC گراف object رو از rootهای برنامه شروع به پیمایش میکنه؛ rootها اشارهگرهایی هستن که objectهایی رو مشخص میکنن که قطعاً در حال استفاده توسط برنامهن. دو مثال از rootها، متغیرهای محلی و متغیرهای global هستن. فرایند پیمایش object graph scanning نامیده میشه. یه عبارت دیگه که ممکنه در داکیومنتهای Go ببینید اینه که یه object reachable هست؛ یعنی object میتونه توسط فرایند scanning کشف بشه. همچنین توجه کنید که با یه استثنا، وقتی حافظه unreachable شد، unreachable باقی میمونه.
این الگوریتم پایه بین همه tracing GCها مشترکه. تفاوت tracing GCها در اینه که وقتی کشف کردن حافظه live هست، چه کاری انجام میدن. GC زبان Go از تکنیک mark-sweep استفاده میکنه؛ یعنی برای پیگیری پیشرفتش، GC مقادیری رو که باهاشون برخورد میکنه بهعنوان live علامتگذاری میکنه. وقتی tracing کامل شد، GC روی تموم حافظه heap قدم میزنه و تموم حافظهای که علامتگذاری نشده رو برای تخصیص در دسترس قرار میده. این فرایند sweeping نامیده میشه.
یه تکنیک جایگزین که ممکنه باهاش آشنا باشید اینه که واقعاً objectها رو به بخش جدیدی از حافظه منتقل کنید و یه forwarding pointer جا بذارید که بعداً برای بهروزرسانی تموم اشارهگرهای برنامه استفاده میشه. به GCی که objectها رو به این روش جابهجا میکنه moving GC میگیم؛ زبان Go یه non-moving GC داره.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: منبع مستندات