انتزاع سهرنگ (The Tricolour Abstraction)
انتزاع سهرنگ یکی از مفاهیم کلیدی در توضیح وضعیت اشیاء در هنگام جمعآوری زباله (Garbage Collection) است. این مفهوم کمک میکند تا الگوریتمهای Collector را بهتر درک کنیم و از درستی آنها مطمئن شویم. بر اساس این انتزاع، اشیای موجود در گراف، به سه دسته تقسیم میشوند:
- سیاه (Black): اشیایی که به طور کامل پردازش شدهاند و Collector دیگر به آنها نیازی ندارد.
- خاکستری (Grey): اشیایی که Collector از وجود آنها آگاه هست ولی هنوز پردازش کامل نشدهاند.
- سفید (White): اشیایی که هنوز توسط Collector بازدید نشدهاند و ممکن است زباله باشند.
فرآیند رنگآمیزی به این ترتیب انجام میشود:
- سایهزدن (Shading): وقتی Collector برای اولین بار به یک شیء سفید برخورد میکند، آن را خاکستری میکند.
- رنگ سیاه: بعد از اینکه Collector تمام فرزندان یک شیء خاکستری را بررسی کرد، آن را سیاه میکند.
- سفید باقی ماندن: اگر یک شیء اصلاً بازدید نشود، سفید میماند و به عنوان زباله در نظر گرفته میشود.
چیزی که در این انتزاع حفظ میشود تا از بروز خطا جلوگیری کند این است که هیچ ارجاعی نباید از یک شیء سیاه، به یک شیء سفید، وجود داشته باشد. اگر این قانون نقض شود، ممکن است یک شیء زنده، علامتگذاری نشود و به اشتباه آزاد شود. به همین دلیل، اگر یک شیء سفید، زنده باشد، حتماً باید از طریق یک شیء خاکستری قابل دسترسی باشد.
این تصویر، یک گراف ساده از اشیاء را نشان میدهد که Collector، در وسط فرآیند Marking است:
- شیء ریشه (Root): سیاه شده و تمام فرزندانش بررسی شدهاند.
- شیء خاکستری: روی پشته علامتگذاری قرار دارد و هنوز پردازش کامل نشدهاست.
- اشیای سفید (A، B، C): هنوز بازدید نشدهاند.
در پایان فرآیند، تمام اشیای زنده (قابل دسترسی) سیاه میشوند. اشیای سفید باقیمانده زباله هستند و میتوانند آزاد شوند. این انتزاع در Collector های همزمان کمک میکند نخهای Mutator، همزمان با Collector اجرا شوند. این انتزاع به Collector کمک میکند تا وضعیت هر شیء را به طور دقیق کنترل کند و مطمئن شود که هیچ شیء زندهای، بهاشتباه آزاد نمیشود.
بهبود الگوریتم Mark-Sweep
یکی از مشکلات اصلی Mark-Sweep این است که عملکردش به رفتار کش (Cache Behavior) سیستم وابستهست و این رفتار معمولاً ضعیف است. برای اینکه بهتر بفهمیم چرا، باید اول درباره کش و محدودیتهای Mark-Sweep صحبت کنیم.
اهمیت کش در عملکرد برنامهها
کش به این دلیل طراحی شدهاست که دسترسی به حافظهی اصلی (RAM)، خیلی کندتر از پردازندهاست. برای مثال:
- حافظه اصلی: دسترسی به یک مقدار، ممکن است صدها سیکل پردازنده طول بکشد.
- سطح اول کش (L1 Cache): دسترسی فقط 3 تا 4 سیکل طول میکشد.
کش از دو ویژگی برنامهها استفاده میکند:
- Temporal Locality: اگر یک آدرس حافظه اخیراً استفاده شده باشد، احتمال دارد دوباره استفاده شود.
- Spatial Locality: اگر یک آدرس حافظه استفاده شود، احتمال دارد آدرسهای مجاورش هم استفاده شوند.
چالشهای Mark-Sweep در استفاده از کش
رفتار Mark-Sweep معمولاً با این اصول محلی بودن سازگار نیست:
- Temporal Locality: در مرحله Mark، هر شیء معمولاً فقط یک بار بازدید میشود و احتمال کمی وجود دارد که دوباره به آن دسترسی پیدا کرد.
- Spatial Locality: دسترسی به اشیاء به صورت تصادفی در گراف اشیاء رخ میدهد و نه به ترتیب مجاورت در حافظه. این باعث می شود که سختافزار نتواند به درستی پیشبارگذاری (Prefetching) را انجام دهد.
چندین روش برای بهبود الگوریتم Mark-Sweep پیشنهاد شدهاست:
بهبود Temporal Locality
کش نوع شیء: اطلاعات نوع (Type Information) اشیاء معمولاً خیلی تکراری است و میتواند در کش ذخیره گردد. این اطلاعات شامل فیلدهای مرجع (Reference Fields) یا کدی برای پردازش اشیاء ست.
افزایش Spatial Locality
مرتبسازی اشیاء در حافظه: اگر اشیاء مرتبط، به صورت متوالی در حافظه قرار گیرند، Spatial Locality بهتر میشود. مثلاً، اشیای فرزند، نزدیک به والدینشان ذخیره شوند.
بازچینی گراف اشیاء: در برخی نسخههای پیشرفته، گراف اشیاء، به صورتی بازچینی میشود که مسیرهای دسترسی بهینهتر باشند.
پیشبارگذاری دادهها
استفاده از دستورهای پیشبارگذاری (Prefetch Instructions): این دستورات به پردازنده کمک میکنند تا دادههای لازم را قبل از نیاز واقعی، به کش بارگذاری کند.
بهرهگیری از کش سلسله مراتبی
چون کشهای سطوح بالاتر (مثل L2 و L3) حجم بیشتری دارند، بهینهسازیها باید به گونهای باشند که دادههای بیشتر، در این کشها نگهداری شوند.
پس با بهبود استفاده از کش، به عملکرد Mark-Sweep کمک میشود تا تأخیرهای دسترسی به حافظه کاهش پیدا کند. مراحل Mark و Sweep سریعتر انجام شوند و پردازنده میتواند کارآیی بهتری داشته باشد.