نگاهی به پشت صحنه ماشینهای مجازی؛ نحوه ذخیرهسازی اشیاء و بهینهسازی دسترسی به ویژگیها — Objects, attributes, maps, oh my
خلاصه
این مقاله نگاهی فنی به نحوه کارکرد Object Shapeها و Inline Cacheها در زبانهای داینامیک دارد. این تکنیکها به موتورهای اجرا کمک میکنند تا دسترسی به ویژگیهای اشیاء را از ساختار کند جدول هش به آرایههای سریع ایندکسشده تبدیل کنند.
در زبانهای برنامهنویسی پویا (Dynamic)، اشیاء معمولاً شبیه به ساختارهای دادهای بزرگی برای نگهداری نام و مقدار هستند. به عنوان مثال، در پایتون یا جاوا اسکریپت، اضافه کردن ویژگیها به یک شیء این حس را به طراحان میدهد که میتوانیم از یک هش تیبل (Hash Table) برای نگهداری ویژگیها استفاده کنیم. اما پیادهسازی ویژگیهای اشیاء با استفاده از جدول هش دارای معایب بزرگی از نظر کارایی است.
چرا جدول هش برای ویژگیهای اشیاء مناسب نیست؟
اگرچه جستجو در هش تیبل از نظر تئوری با مرتبه O(1) انجام میشود، اما در عمل مشکلاتی دارد:
- عملیات کند: فرآیند هش کردن و پروب کردن (Probing) روی حافظه بسیار زمانبر است.
- غیرمستقیم بودن دسترسی (Indirection): برای دسترسی به یک صفت، باید ابتدا به شیء مراجعه کرد و سپس به حافظه جداگانه مربوط به هش تیبل پرید. این کار باعث بارگذاریهای وابسته روی حافظه و در نتیجه پرشهای کش (Cache Miss) میشود.
برای حل این مشکل، طراحان مفسرها و ماشینهای مجازی از مفهوم Object Shapes (یا همان Map، Layout، Hidden Classes و Structures) استفاده میکنند تا خواندن، نوشتن و ذخیرهسازی ویژگیها را بهینه کنند.
مفهوم Mapها و ساختار درختی
هدف اصلی سیستمهای مبتنی بر Map این است که از کار کردن با رشتهها و هش کردن در مسیر سریع (Fast Path) اجتناب کنند و در عوض با اعداد صحیح کوچک کار کنند. همچنین میخواهیم ویژگیها را بهصورت درونخطی (In-line) در خود شیء ذخیره کنیم تا نیازی به دسترسیهای اضافه به حافظه نباشد.
وقتی ویژگی جدیدی به یک شیء اضافه میشود، Map آن تغییر میکند. این Mapها در واقع ساختارهای درختی هستند که به صورت ساختارهای دادهای پایدار (Persistent Data Structures) تخصیص داده میشوند. هر شیء با یک Map خالی شروع میشود و با اضافه کردن صفتها، مسیر درخت را برای یافتن یا ساختن Map جدید دنبال میکند.
کش درونخطی یا Inline Cache
برای اینکه سرعت اجرای برنامهها به حداکثر برسد، از مکانیسمی به نام Inline Cache استفاده میشود. در این روش، مفسر یک مبادله حافظه و سرعت انجام میدهد:
- اگر شیء مورد نظر دارای همان Map قبلی باشد، مستقیماً از روی ایندکسِ ذخیرهشده در حافظه خوانده میشود.
- در غیر این صورت، جستجوی اصلی بر اساس نام رشتهای انجام شده و ترکیب Map و ایندکس برای دفعات بعدی کش میشود.
نکته جالب اینجاست که برای بررسی تطابق Mapها، از برابری اشارهگرها (is در پایتون) به جای مقایسه عمیق ویژگیها استفاده میشود. از آنجا که این Mapها تغییرناپذیر (Immutable) هستند، مقایسه اشارهگرها بسیار سریع انجام میگیرد.
چرا این موضوع برای مهندسان نرمافزار مهم است؟
آگاهی از نحوه کارکرد Shapeها و Inline Cacheها به توسعهدهندگان کمک میکند تا درک بهتری از عملکرد زیرساخت زبانهایی که استفاده میکنند داشته باشند. برای مثال، ترتیب اضافهکردن صفتها در شیء یا مقداردهیهای شرطی میتواند ساختار Mapها را تغییر دهد و روی بهینهسازیهای JIT و مفسر تأثیر بگذارد (همانطور که در پایتون ویژگیهایی مثل __slots__ رویکردهای مشابهی را برای مدیریت حافظه ارائه میدهند).
این مطلب بهصورت کاملاً خودکار و با هوش مصنوعی از مقالهٔ اصلی و بحث هکرنیوز تهیه شده و ممکن است خطا داشته باشد. حقوق مطلب اصلی متعلق به نویسندهٔ آن است. قوانین و گزارش مشکل