مقدمه
بنابراین ایمیلی دریافت میکنید که میگوید بازی تحت وب / برنامه وب شما پساز مدت زمان مشخصی عملکرد بدی دارد، کدتان را بررسی میکنید، چیزی که برجسته باشد نمیبینید، تا اینکه ابزارهای عملکرد حافظه Chrome را باز میکنید و این را میبینید:
یکی از همکارانتان میخندد، چون متوجه میشود که مشکل عملکردی مربوط به حافظه دارید.
در نمای نمودار حافظه، این الگوی دندانارهای نشاندهنده یک مشکل عملکردی بالقوه بحرانی است. با افزایش استفاده از حافظه، خواهید دید که ناحیه نمودار نیز در ضبط خط زمان افزایش مییابد. وقتی نمودار بهطور ناگهانی افت میکند، نمونهای است که Garbage Collector اجرا شده و اشیای حافظه ارجاعشده شما را پاک کرده است.
در نموداری مثل این، میتوانید ببینید که رویدادهای «جمعآوری زباله» زیادی درحال رخ دادن است که میتواند برای عملکرد برنامههای وب شما مضر باشد. این مقاله درباره نحوه کنترل استفاده از حافظه و کاهش تأثیر آن بر عملکرد شما صحبت خواهد کرد.
هزینههای جمعآوری زباله و عملکرد
مدل حافظه جاوا اسکریپت بر فناوریای بهنام Garbage Collector (زبالهروب) ساخته شده است. در بسیاری از زبانها، برنامهنویس مستقیماً مسئول تخصیص و آزادسازی حافظه از توده حافظه سیستم است. بااینحال، سیستم «زبالهروب» این وظیفه را ازطرف برنامهنویس مدیریت میکند، یعنی وقتی برنامهنویس ارجاع شیء را لغو میکند، شیء مستقیماً از حافظه آزاد نمیشود، بلکه در زمان دیگری که اکتشافات «زبالهروب» تصمیم میگیرد که انجام این کار مفید است، آزاد میشود. این فرایند تصمیمگیری مستلزم آن است که «زبالهروب» برخی تجزیهوتحلیلهای آماری را روی اشیاء فعال و غیرفعال اجرا کند که انجام آن مدتی طول میکشد.
جمعآوری زباله اغلب بهعنوان نقطه مقابل مدیریت دستی حافظه نشان داده میشود که در آن برنامهنویس باید مشخص کند کدام اشیاء باید تخصیصزدایی شوند و به سیستم حافظه برگردند
فرایندی که در آن GC حافظه را بازیابی میکند رایگان نیست، معمولاً با اختصاص دادن یک بلوک زمانی برای انجام کار خود، عملکرد دردسترس شما را کاهش میدهد؛ در کنار آن، خود سیستم تصمیم میگیرد که چه زمانی اجرا شود. شما هیچ کنترلی بر این کنش ندارید، پالس GC میتواند در هر زمانی درطول اجرای کد رخ دهد، که اجرای کد را تا زمان تکمیل شدن مسدود میکند. مدت این پالس معمولاً برای شما ناشناخته است؛ بسته به اینکه برنامهتان در هر نقطه مشخص چگونه از حافظه استفاده میکند، اجرای آن مدتی طول میکشد.
برنامههای با عملکرد بالا به مرزهای عملکردی یکپارچه متکی هستند تا تجربه روانی را برای کاربران تضمین کنند. سیستمهای جمعآوری زباله میتوانند این هدف را دور بزنند، زیرا میتوانند در زمانهای تصادفی برای مدتهای تصادفی اجرا شوند و زمان دردسترسی را که برنامه برای رسیدن به اهداف عملکردی خود نیاز دارد، مصرف کنند.
کاهش ریزش حافظه، کاهش مالیات جمعآوری زباله
همانطور که اشاره شد، پالس GC زمانی رخ میدهد که مجموعهای از اکتشافات تعیین کند که اشیای غیرفعال کافی وجود دارد که پالس مفید باشد. بنابراین، کلید کاهش مدت زمانی که «زبالهروب» از برنامه شما میگیرد در حذف هرچه بیشتر موارد ایجاد و انتشار بیشازحد شیء است. این فرایند ایجاد/آزادسازی مکرر شیء «ریزش حافظه» نامیده میشود. اگر بتوانید ریزش حافظه را درطول عمر برنامه کاهش دهید، مدت زمانی را که GC از اجرای شما میگیرد نیز کاهش میدهید. این یعنی باید تعداد اشیاء ایجادشده و ازبینرفته را حذف/ کاهش دهید، درواقع باید اختصاص حافظه را متوقف کنید.
این فرایند نمودار حافظه شما را از این دستگاه منتقل خواهد کرد :
به این:
در این مدل، میتوانید ببینید که نمودار دیگر الگوی دندانهدار ندارد، بلکه در ابتدا رشد زیادی دارد و سپس بهتدریج درطول زمان افزایش مییابد. اگر بهدلیل ریزش حافظه با مشکلات عملکردی مواجه هستید، این نوع نموداری است که میخواهید ایجاد کنید.
حرکت بهسمت جاوا اسکریپت حافظه ایستا
جاوا اسکریپت حافظه ایستا تکنیکی است که شامل پیشتخصیص کل حافظه موردنیاز برای طول عمر برنامه در ابتدای برنامه و مدیریت آن حافظه درطول اجرا در زمانی است که دیگر به اشیاء نیازی نیست. با چند مرحله ساده میتوانیم به این هدف نزدیک شویم:
- برنامهتان را مجهز کنید تا حداکثر تعداد اشیای حافظه زنده موردنیاز (بهازای هر نوع) را برای طیفی از سناریوهای استفاده تعیین کند
- کدتان را دوباره پیادهسازی کنید تا آن حداکثر مقدار را ازقبل اختصاص دهد، و سپس بهجای رفتن به حافظه اصلی، آنها را بهصورت دستی واکشی/ رها کنید.
درواقع، برای انجام مورد ۱ باید کمی از مورد ۲ را انجام دهیم، پس بیایید از آنجا شروع کنیم.
منبع شیء
به زبان ساده، ادغام اشیاء فرایند نگهداری مجموعهای از اشیاء استفادهنشده است که نوع مشترکی دارند. وقتی برای کدتان به شیء جدیدی نیاز دارید، بهجای اختصاص دادن شیء جدیدی از توده حافظه سیستم، یکی از اشیاء استفادهنشده را از استخر بازیافت میکنید. وقتی کد خارجی کارش با شیء تمام میشود، بهجای اینکه آن را در حافظه اصلی رها کند، آن را به استخر برمیگرداند. چون شیء هرگز از کد ارجاعزدایی (یا حذف) نمیشود، زبالهروبی نخواهد شد. استفاده از استخرهای شیء کنترل حافظه را به دست برنامهنویس بازمیگرداند و تأثیر جمعآوری زباله بر عملکرد را کاهش میدهد.
ازآنجاییکه مجموعهای ناهمگن از انواع شیء وجود دارد که برنامه حفظ میکند، استفاده صحیح از مخزنهای شیء مستلزم این است که برای هر نوعی که درطول زمان اجرای برنامه شما ریزش بالایی را تجربه میکند، یک مخزن داشته باشید.
var newEntity = gEntityObjectPool.allocate();
newEntity.pos = {x: 215, y: 88};
//..... do some stuff with the object that we need to do
gEntityObjectPool.free(newEntity); //free the object when we're done
newEntity = null; //free this object reference
برای اکثر برنامهها، درنهایت ازنظر نیاز به اختصاص دادن اشیاء جدید به سطحی ثابت خواهید رسید. با چندین بار اجرای برنامهتان، باید بتوانید حس خوبی نسبت به این حد بالا داشته باشید و میتوانید آن تعداد شیء را در شروع برنامهتان ازقبل اختصاص دهید.
پیشاختصاص دادن اشیاء
پیادهسازی ادغام اشیاء در پروژهتان حداکثر نظری را برای تعداد اشیاء موردنیاز درطول زمان اجرای برنامهتان به شما میدهد. پساز اجرای سایتتان در سناریوهای آزمایشی مختلف، میتوانید درک خوبی از انواع الزامات حافظه موردنیاز بهدست آورید و میتوانید آن دادهها را در جایی فهرستبندی کنید و آن را تجزیهوتحلیل کنید تا متوجه شوید حدود بالای الزامات حافظه برای برنامه شما چیست.
سپس، در نسخه ارسالی برنامهتان، میتوانید فاز مقداردهی اولیه را برای پیشتکمیل همه مخازن شیء تا مقدار مشخصی تنظیم کنید. این کار همه مقداردهیهای اولیه شیء را به جلوی برنامه شما منتقل میکند و مقدار تخصیصهایی را که بهطور پویا درطول اجرای آن رخ میدهد کاهش میدهد.
function init() {
//preallocate all our pools.
//Note that we keep each pool homogeneous wrt object types
gEntityObjectPool.preAllocate(256);
gDomObjectPool.preAllocate(888);
}
مقداری که انتخاب میکنید ارتباط زیادی با رفتار برنامهتان دارد؛ گاهیاوقات حداکثر نظری بهترین گزینه نیست. برای مثال، انتخاب میانگین حداکثر ممکن است ردپای حافظه کوچکتری برای کاربران غیرحرفهای به شما بدهد.
اصلاً راهحل جادویی نیست
طبقهبندی کاملی از برنامهها وجود دارد که در آنها الگوهای رشد حافظه ایستا میتواند یک برد باشد. اما همانطور که Renato Mangini، همکار Chrome DevRel، اشاره میکند، چند اشکال وجود دارد.
نتیجهگیری
یکی از دلایلی که جاوا اسکریپت را برای وب ایدهآل میکند این است که زبانی سریع، سرگرمکننده و آسان برای شروع است. این امر عمدتاً بهدلیل مانع کم آن دربرابر محدودیتهای نحو و مدیریت مشکلات حافظه ازجانب شما است. میتوانید کدنویسی کنید و بگذارید کارهای سخت را انجام دهد. اما برای برنامههای وب با عملکرد بالا، مثل بازیهای HTML5، GC اغلب میتواند نرخ فریم بسیار ضروری را کاهش دهد و تجربه کاربر نهایی را تضعیف کند. با ابزار دقیق و دقیق و استفاده از استخرهای شی، میتوانید این بار را بر روی نرخ فریم خود کاهش دهید و آن زمان را برای چیزهای عالیتر پس بگیرید.
کد منبع
پیادهسازیهای زیادی از استخرهای شیء در وب وجود دارد، بنابراین شما را با یک پیادهسازی دیگر خسته نمیکنم. درعوض، شما را به این موارد ارجاع میدهم که هرکدام تفاوتهای ظریفی در پیادهسازی دارند؛ این موضوع مهم است، زیرا هر کاربرد برنامه ممکن است نیازهای پیادهسازی خاصی داشته باشد.
- مجموعه اشیای Gamecore.js
- Beej’s Object Pools
- Emehrkay’s super simple object pool
- مجموعه اشیای متمرکز بر بازی Steven Lambert
- راهاندازی objectPool مربوط به RenderEngine