مقدمة
على الرغم من أنّ JavaScript تستخدِم ميزة جمع المهملات لإدارة الذاكرة تلقائيًا، إلا أنّها ليست بديلاً عن إدارة الذاكرة الفعّالة في التطبيقات. تواجه تطبيقات JavaScript المشاكل نفسها المتعلقة بالذاكرة التي تواجهها التطبيقات الأصلية، مثل تسرُّب الذاكرة وتضخمها، ومع ذلك عليها أيضًا التعامل مع فترات التوقف في جمع المهملات. تواجه التطبيقات الكبيرة مثل Gmail المشاكل نفسها التي تواجهها التطبيقات الأصغر حجمًا. اطّلِع على المقالة لمعرفة كيف استخدم فريق Gmail "أدوات مطوّري البرامج في Chrome" لتحديد مشاكل الذاكرة وعزلها وحلّها.
جلسة Google I/O 2013
وقد قدّمنا هذه المادة في مؤتمر Google I/O لعام 2013. يمكنك الاطّلاع على الفيديو أدناه:
Gmail، واجهتنا مشكلة…
كان فريق Gmail يواجه مشكلة خطيرة. كان يتم سماع قصص متزايدة عن علامات تبويب Gmail التي تستهلك عدة غيغابايت من الذاكرة على أجهزة الكمبيوتر المحمول المكتبي ذات الموارد المحدودة، وغالبًا ما كان ينتهي الأمر بإغلاق المتصفّح بالكامل. قصص عن وحدات المعالجة المركزية التي يتم تثبيتها بنسبة %100 والتطبيقات التي لا تستجيب وعلامات التبويب في Chrome التي لا تعمل ("لقد مات، يا شريف"). لم يكن لدى الفريق أي فكرة عن كيفية بدء تشخيص المشكلة، ناهيك عن حلّها. لم تكن لديهم فكرة عن مدى انتشار المشكلة، ولم تكن الأدوات المتاحة مناسبة للتطبيقات الكبيرة. تعاون الفريق مع فِرق Chrome، وطوّروا معًا أساليب جديدة لتحديد أولويات مشاكل الذاكرة وتحسين الأدوات الحالية وتفعيل جمع بيانات الذاكرة من الميدان. ولكن قبل الانتقال إلى الأدوات، لنطّلِع على أساسيات إدارة ذاكرة JavaScript.
أساسيات إدارة الذاكرة
قبل أن تتمكّن من إدارة الذاكرة بفعالية في JavaScript، عليك فهم الأساسيات. سيتناول هذا القسم الأنواع الأساسية ورسم بياني للعناصر، كما سيقدّم تعريفات لتضخم الذاكرة بشكل عام وتسرُّب الذاكرة في JavaScript. يمكن تصور الذاكرة في JavaScript على أنّها رسم بياني، ولهذا السبب تلعب نظرية الرسم البياني دورًا في إدارة الذاكرة في JavaScript وأداة تحليل الذاكرة.
الأنواع الأساسية
تحتوي JavaScript على ثلاثة أنواع أساسية:
- رقم (مثل 4 أو 3.14159)
- منطقية (صحيح أو خطأ)
- سلسلة ("مرحبًا")
لا يمكن أن تشير هذه الأنواع البدائية إلى أي قيم أخرى. في الرسم البياني للعناصر، تكون هذه القيم دائمًا عقدًا ورقية أو نهائية، ما يعني أنّها لا تحتوي أبدًا على حافة خارجية.
هناك نوع واحد فقط من الحاويات: العنصر. في JavaScript، يكون الكائن مصفوفة ترابطية. العنصر غير الفارغ هو عقدة داخلية لها حواف خارجية تؤدي إلى قيم (عقد) أخرى.
ماذا عن الصفائف؟
الصفيف في JavaScript هو في الواقع كائن يحتوي على مفاتيح رقمية. هذا تبسيط، لأنّ أوقات تشغيل JavaScript ستُحسِّن الكائنات المشابهة للمصفوفات وتمثلها في الخلفية كمصفوفات.
المصطلحات
- القيمة: مثيل لنوع أساسي أو كائن أو مصفوفة أو غير ذلك
- المتغيّر: اسم يشير إلى قيمة.
- السمة: اسم في عنصر يشير إلى قيمة.
الرسم البياني للكائنات
جميع القيم في JavaScript هي جزء من الرسم البياني للكائنات. يبدأ الرسم البياني بالعناصر الأساسية، مثل كائن النافذة. لا يمكنك التحكّم في مدة صلاحية جذر ذاكرة التخزين المؤقت في Chrome، لأنّ المتصفّح هو الذي ينشئها ويدمرها عند إزالة تحميل الصفحة. المتغيّرات الشاملة هي في الواقع خصائص في النافذة.
متى تصبح القيمة غير صالحة؟
تصبح القيمة غير صالحة عندما لا يتوفّر مسار من الجذر إلى القيمة. بعبارة أخرى، بدءًا من الجذور والبحث الشامل عن جميع سمات وعناصر "العنصر" النشطة في إطار الحزمة، لا يمكن الوصول إلى قيمة، لأنّها أصبحت غير صالحة.
ما هو تسرّب الذاكرة في JavaScript؟
يحدث تسرُّب الذاكرة في JavaScript غالبًا عندما تكون هناك عقد DOM لا يمكن الوصول إليها من شجرة DOM للصفحة، ولكن لا يزال يتمّ الإشارة إليها من خلال عنصر JavaScript. على الرغم من أنّ المتصفّحات الحديثة تصعِّب بشكل متزايد حدوث عمليات تسرُّب غير مقصودة، إلا أنّ ذلك لا يزال أسهل مما قد يتوقّع المرء. لنفترض أنّك أضفت عنصرًا إلى شجرة نموذج DOM على النحو التالي:
email.message = document.createElement("div");
displayList.appendChild(email.message);
وفي وقت لاحق، يمكنك إزالة العنصر من قائمة العرض:
displayList.removeAllChildren();
ما دام email
متوفّرًا، لن تتم إزالة عنصر DOM المُشار إليه في الرسالة، حتى لو تم فصله الآن عن شجرة DOM للصفحة.
ما هو المقصود بـ "التطبيقات المُثقلة بالميزات"؟
تكون صفحتك مفرطة في الحجم عندما تستخدم ذاكرة أكثر من اللازم لتحقيق السرعة المثلى للصفحة. ويؤدي تسرب الذاكرة أيضًا إلى زيادة حجم التطبيق بشكل غير مباشر، ولكن ليس عن قصد. إنّ ذاكرة التخزين المؤقت للتطبيق التي لا تفرض أي حدود على الحجم هي مصدر شائع لتضخم الذاكرة. يمكن أن تكون صفحتك أيضًا مثقلة ببيانات المضيف، مثل بيانات البكسل المحمَّلة من الصور.
ما هو جمع القمامة؟
جمع المهملات هو الطريقة التي يتم بها استرداد الذاكرة في JavaScript. ويحدِّد المتصفّح وقت حدوث ذلك. أثناء عملية جمع البيانات، يتم تعليق جميع عمليات تنفيذ النصوص البرمجية على صفحتك أثناء اكتشاف القيم النشطة من خلال تنقّل في رسم بياني للعناصر بدءًا من الجذور في ذاكرة التخزين المؤقت. يتم تصنيف جميع القيم التي لا يمكن الوصول إليها على أنّها غير صالحة. يستعيد مدير الذاكرة مساحة الذاكرة المخصّصة للقيم غير الصالحة.
أداة جمع القمامة V8 بالتفصيل
للتعرّف بشكل أفضل على كيفية جمع المهملات، لنلقِ نظرة على أداة جمع المهملات V8 بالتفصيل. يستخدم V8 أداة جمع جيلية. تنقسم الذاكرة إلى جيلَين: الجيل الشاب والجيل القديم. إنّ عملية التوزيع والجمع داخل الجيل الشاب سريعة ومتكررة. تكون عملية التخصيص والجمع ضمن الجيل القديم أبطأ وأقل تكرارًا.
أداة جمع الجيل التالي
يستخدم V8 أداة جمع من جيلَين. يتم تحديد عمر القيمة على أنّه عدد وحدات البايت التي تم تخصيصها منذ تخصيصها. من الناحية العملية، غالبًا ما يتم تقريب عمر القيمة حسب عدد مجموعات الجيل الجديد التي نجت منها. بعد أن تصبح القيمة قديمة بما يكفي، يتم نقلها إلى الجيل القديم.
من الناحية العملية، لا تبقى القيم التي تم تخصيصها حديثًا لفترة طويلة. أظهرت دراسة أجريت على برامج Smalltalk أنّ 7% فقط من القيم تبقى محفوظة بعد جمع الجيل الجديد. تبيّن من خلال دراسات مشابهة على جميع أوقات التشغيل أنّه في المتوسط، ما بين% 90 و% 70 من القيم التي تم تخصيصها حديثًا لا يتم أبدًا نقلها إلى الجيل القديم.
جيل الشباب
يتم تقسيم ذاكرة التخزين المؤقت لإنشاء الجيل الجديد في V8 إلى مساحتَين، وهما from وto. يتم تخصيص الذاكرة من المساحة إلى. يتمّ التخصيص بسرعة كبيرة، إلى أن تمتلئ المساحة، وعندها يتمّ تشغيل مجموعة من الجيل الجديد. تبدّل عملية جمع الجيل الجديد أولاً مساحة "من" ومساحة "إلى"، ويتم فحص مساحة "إلى" القديمة (المساحة "من" الآن) ويتم نسخ جميع القيم النشطة إلى مساحة "إلى" أو نقلها إلى الجيل القديم. تستغرق عملية جمع الجيل الجديد عادةً 10 مللي ثانية (ms).
من البديهي أنّ كل عملية تخصيص يجريها تطبيقك تقرب من استنفاد المساحة وتؤدي إلى إيقاف مؤقت لعملية جمع القمامة. ملاحظة لمطوّري الألعاب: لضمان مدة عرض لقطة تبلغ 16 ملي ثانية (مطلوب لتحقيق 60 لقطة في الثانية)، يجب ألا يُجري تطبيقك أي عمليات تخصيص، لأنّ عملية جمع واحدة لجيل جديد ستستهلك معظم مدة عرض اللقطة.
الجيل القديم
يستخدم جيل الذاكرة المؤقت القديم في V8 خوارزمية وضع العلامات والتجميع لجمع البيانات. تحدث عمليات تخصيص الجيل القديم كلما تم نقل قيمة من الجيل الجديد إلى الجيل القديم. كلما حدثت عملية جمع لجيل قديم، يتم أيضًا جمع جيل جديد. سيتم إيقاف تطبيقك مؤقتًا لبضع ثوانٍ. وفي الممارسة العملية، هذا أمر مقبول لأنّ مجموعات الجيل القديم نادرة.
ملخّص ميزة "جمع القمامة" في V8
إنّ إدارة الذاكرة التلقائية باستخدام ميزة جمع المهملات رائعة لزيادة إنتاجية المطوّرين، ولكن في كل مرة تخصص فيها قيمة، تقترب أكثر من إيقاف جمع المهملات مؤقتًا. يمكن أن تؤدي الفواصل في عملية جمع المهملات إلى إفساد تجربة استخدام تطبيقك من خلال إدخال تقطُّع في الأداء. الآن بعد أن تعرّفت على كيفية إدارة JavaScript للذاكرة، يمكنك اتخاذ الخيارات المناسبة لتطبيقك.
حلّ المشاكل في Gmail
خلال العام الماضي، تمّت إضافة العديد من الميزات وإصلاح الأخطاء إلى أدوات مطوّري البرامج في Chrome، ما جعلها أكثر فعالية من أي وقت مضى. بالإضافة إلى ذلك، أجرى المتصفّح نفسه تغييرًا رئيسيًا في واجهة برمجة التطبيقات performance.memory API، ما أتاح لتطبيق Gmail وأي تطبيق آخر جمع إحصاءات الذاكرة من الحقل. وباستخدام هذه الأدوات الرائعة، أصبحت المهمة التي كانت تبدو مستحيلة في السابق لعبة مثيرة لتتبُّع الجناة.
الأدوات والأساليب
Field Data وperformance.memory API
اعتبارًا من الإصدار 22 من Chrome، يتم تفعيل واجهة برمجة التطبيقات performance.memory API تلقائيًا. بالنسبة إلى التطبيقات التي تعمل لفترة طويلة، مثل Gmail، تكون البيانات الواردة من المستخدمين الحقيقيين قيّمة للغاية. تتيح لنا هذه المعلومات التمييز بين المستخدمين المكثّفين، الذين يقضون من 8 إلى 16 ساعة يوميًا على Gmail ويتلقّون مئات الرسائل يوميًا، والمستخدمين العاديين الذين يقضون بضع دقائق يوميًا في Gmail ويتلقّون 12 رسالة أو نحو ذلك في الأسبوع.
تعرض واجهة برمجة التطبيقات هذه ثلاث قطع من البيانات:
- jsHeapSizeLimit: مقدار الذاكرة (بالبايت) التي تقتصر عليها كومة JavaScript.
- totalJSHeapSize: مقدار الذاكرة (بالبايت) التي خصصتها ذاكرة JavaScript، بما في ذلك المساحة الفارغة
- usedJSHeapSize: مقدار الذاكرة (بالبايت) المستخدَمة حاليًا
تجدر الإشارة إلى أنّ واجهة برمجة التطبيقات تعرض قيم الذاكرة لعملية Chrome بأكملها. على الرغم من أنّه ليس الوضع التلقائي، قد يفتح Chrome علامات تبويب متعدّدة في عملية عرض الصفحة نفسها في ظلّ ظروف معيّنة. وهذا يعني أنّ القيم التي تعرضها performance.memory قد تحتوي على مساحة الذاكرة المستخدَمة في علامات التبويب الأخرى للمتصفّح بالإضافة إلى تلك التي تحتوي على تطبيقك.
قياس الذاكرة على نطاق واسع
أعدّ فريق Gmail لغة JavaScript لاستخدام واجهة برمجة التطبيقات performance.memory API لجمع معلومات الذاكرة مرة واحدة كل 30 دقيقة تقريبًا. ولأنّ العديد من مستخدمي Gmail يتركون التطبيق مفتوحًا لعدة أيام في المرة الواحدة، تمكّن الفريق من تتبُّع زيادة استخدام الذاكرة بمرور الوقت، بالإضافة إلى إحصاءات إجمالي مساحة الذاكرة المستخدَمة. في غضون بضعة أيام من تجهيز Gmail لجمع معلومات عن الذاكرة من عيّنة عشوائية من المستخدمين، حصل الفريق على بيانات كافية لفهم مدى انتشار مشاكل الذاكرة بين المستخدمين العاديين. لقد حدّدوا مرجعًا واستخدَموا بث البيانات الواردة لتتبُّع مستوى التقدّم نحو تحقيق هدف تقليل استهلاك الذاكرة. وفي النهاية، سيتم استخدام هذه البيانات أيضًا لرصد أي تراجع في ذاكرة التطبيق.
بالإضافة إلى أغراض التتبّع، تقدّم القياسات الميدانية أيضًا إحصاءات دقيقة عن الارتباط بين مساحة الذاكرة المُستخدَمة وأداء التطبيق. على عكس الاعتقاد الشائع بأنّ "زيادة سعة الذاكرة تؤدي إلى تحسين الأداء"، اكتشف فريق Gmail أنّه كلما زادت مساحة الذاكرة المستخدَمة، زادت أوقات الاستجابة لإجراءات Gmail الشائعة. بعد أن عرف الفريق ذلك، أصبح أكثر تحفيزًا من أي وقت مضى للحد من استهلاك الذاكرة.
تحديد مشكلة في الذاكرة باستخدام "مخطّط زمني" في "أدوات مطوّري البرامج"
الخطوة الأولى في حلّ أي مشكلة في الأداء هي إثبات وجود المشكلة، وإنشاء اختبار يمكن تكراره، وإجراء قياس أساسي للمشكلة. بدون برنامج قابل للتكرار، لا يمكنك قياس المشكلة بشكل موثوق. بدون قياس أساسي، لن تعرف مقدار تحسين الأداء.
لوحة "المخطط الزمني" في أدوات مطوّري البرامج هي مرشح مثالي لإثبات وجود المشكلة. وتوفّر نظرة عامة كاملة على الوقت المستغرَق في تحميل تطبيق الويب أو صفحته والتفاعل معه. يتمّ رسم جميع الأحداث على مخطط زمني، بدءًا من تحميل الموارد إلى تحليل JavaScript وحساب الأنماط ووقت الاستراحة في جمع المهملات وإعادة الطلاء. لأغراض التحقيق في مشاكل الذاكرة، تتضمّن لوحة "المخطط الزمني" أيضًا وضع "الذاكرة" الذي يتتبّع إجمالي الذاكرة المخصّصة وعدد عقد DOM وعدد كائنات النوافذ وعدد أدوات الاستماع إلى الأحداث المخصّصة.
إثبات وجود مشكلة
ابدأ بتحديد تسلسل الإجراءات التي تشتبه في أنّها تؤدي إلى تسرب الذاكرة. ابدأ بتسجيل المخطط الزمني، ثم نفِّذ تسلسل الإجراءات. استخدِم زر "المهملات" في أسفل الصفحة لفرض جمع كامل للملفّات غير الضرورية. إذا ظهر لك رسم بياني على شكل سن المنشار بعد بضع تكرارات، يعني ذلك أنّك تخصص الكثير من العناصر التي تعيش لفترة قصيرة. ولكن إذا لم يكن من المتوقّع أن يؤدّي تسلسل الإجراءات إلى الاحتفاظ بأي ذاكرة، ولم ينخفض عدد عقد DOM إلى العدد الأساسي الذي بدأت به، يكون لديك سبب وجيه للاشتباه في حدوث تسرُّب.
بعد التأكّد من وجود المشكلة، يمكنك الحصول على مساعدة في تحديد مصدرها من خلال أداة تحليل الذاكرة في أدوات مطوّري البرامج.
العثور على عمليات تسرّب الذاكرة باستخدام أداة تحليل الذاكرة في أدوات مطوّري البرامج
توفّر لوحة "أداة تحليل الأداء" أداة تحليل أداء وحدة المعالجة المركزية وأداة تحليل أداء الذاكرة. تعمل ميزة تحليل الذاكرة العشوائية عن طريق أخذ لقطة شاشة لرسم بياني للعناصر. قبل التقاط لقطة، يتم جمع القمامة من الأجيال الشابة والقديمة. بعبارة أخرى، لن تظهر لك سوى القيم التي كانت نشطة عند أخذ اللقطة.
هناك الكثير من الوظائف في أداة تحليل الذاكرة التي لا يمكن تغطيتها بشكل كافٍ في هذه المقالة، ولكن يمكن العثور على مستندات تفصيلية على موقع "مطوّرو تطبيقات Chrome" الإلكتروني. سنركّز هنا على أداة تحليل "تخصيص مساحة الذاكرة".
استخدام أداة تحليل تخصيص الذاكرة
يجمع أداة تحليل "تخصيص الذاكرة" معلومات اللقطة التفصيلية لأداة تحليل "تخصيص الذاكرة" مع التحديث المتزايد وتتبُّع لوحة "المخطط الزمني". افتح لوحة "الملفات الشخصية"، وابدأ ملفًا شخصيًا من النوع تسجيل عمليات تخصيص الذاكرة، ثم نفِّذ سلسلة من الإجراءات، ثم أوقِف التسجيل للتحليل. يأخذ أداة تحليل عمليات التخصيص لقطات ذاكرة عشوائية بشكل دوري طوال عملية التسجيل (بمعدل كل 50 ملي ثانية) ولقطة نهائية واحدة في نهاية التسجيل.
تشير الأشرطة في أعلى الشاشة إلى العثور على عناصر جديدة في الحِزمة. يتوافق ارتفاع كل شريط مع حجم العناصر التي تم تخصيصها مؤخرًا، ويشير لون الأشرطة إلى ما إذا كانت هذه العناصر لا تزال نشطة في لقطة الذاكرة المؤقتة النهائية أم لا: تشير الأشرطة الزرقاء إلى العناصر التي لا تزال نشطة في نهاية المخطط الزمني، وتشير الأشرطة الرمادية إلى العناصر التي تم تخصيصها خلال المخطط الزمني، ولكن تم جمع المهملات منذ ذلك الحين.
في المثال أعلاه، تم تنفيذ إجراء 10 مرات. يخزّن نموذج البرنامج خمسة عناصر، لذا من المتوقّع ظهور آخر خمسة أشرطة زرقاء. يشير الشريط الأزرق الأيمن إلى مشكلة محتملة. يمكنك بعد ذلك استخدام أشرطة التمرير في المخطط الزمني أعلاه لتكبير هذه اللقطة المحدّدة والاطّلاع على العناصر التي تم تخصيصها مؤخرًا في تلك المرحلة. سيؤدي النقر على عنصر معيّن في الحِزمة إلى عرض شجرة الاحتفاظ به في الجزء السفلي من لقطة الحِزمة. من المفترض أن يمنحك فحص مسار الاحتفاظ بالعنصر معلومات كافية لفهم سبب عدم جمع العنصر، ويمكنك إجراء التغييرات اللازمة على الرمز البرمجي لإزالة الإشارة غير الضرورية.
حلّ مشكلة نقص الذاكرة في Gmail
باستخدام الأدوات والأساليب المذكورة أعلاه، تمكّن فريق Gmail من تحديد بعض فئات الأخطاء: ذاكرات التخزين المؤقت غير المحدودة، وصفائف متزايدة بلا حدود من عمليات الاستدعاء في انتظار حدوث أمر لا يحدث أبدًا، ومستمعِي الأحداث الذين يحتفظون عن غير قصد بأهدافهم. ومن خلال حلّ هذه المشاكل، انخفض إجمالي استخدام الذاكرة في Gmail بشكل كبير. استخدم المستخدمون في الشريحة التي تبلغ نسبتها% 99 ذاكرة أقل بنسبة% 80 مقارنةً بالماضي، كما انخفض استهلاك الذاكرة لدى المستخدمين المتوسطين بنسبة %50 تقريبًا.
وبما أنّ Gmail يستخدم ذاكرة أقل، تم تقليل وقت الاستجابة أثناء التوقف المؤقت لعملية جمع القمامة، ما أدى إلى تحسين تجربة المستخدم بشكل عام.
من الجدير بالذكر أيضًا أنّ فريق Gmail جمع إحصاءات عن استخدام الذاكرة، ما سمح له باكتشاف تراجعات في عملية جمع المهملات داخل Chrome. على وجه التحديد، تم اكتشاف خطأَين في التجزئة عندما بدأت بيانات ذاكرة Gmail في عرض زيادة كبيرة في الفجوة بين إجمالي الذاكرة المخصّصة والذاكرة النشطة.
الحث على اتخاذ إجراء
اطرح على نفسك الأسئلة التالية:
- ما مقدار الذاكرة التي يستخدمها تطبيقي؟ من المحتمل أنّك تستخدِم مساحة كبيرة جدًا من الذاكرة، ما يؤثّر سلبًا في الأداء العام للتطبيق، على عكس الاعتقاد الشائع. من الصعب معرفة العدد الصحيح بالضبط، ولكن احرص على التأكّد من أنّ أيّ ذاكرة تخزين مؤقت إضافية تستخدمها صفحتك لها تأثير قياسي في الأداء.
- هل صفحتي خالية من أي تسرُّب للمعلومات؟ إذا كانت صفحتك تتضمّن عمليات تسرُّب للذاكرة، قد لا يؤثّر ذلك في أداء صفحتك فقط، بل في علامات التبويب الأخرى أيضًا. استخدِم أداة تتبُّع الأجسام للمساعدة في التركيز على أي تسرُّبات.
- ما هو معدّل تكرار تنظيف "محرّك بحث Google" لصفحتي؟ يمكنك الاطّلاع على أي عمليات إيقاف مؤقت لخدمة "بحث Google" باستخدام لوحة المخطط الزمني في أدوات مطوّري برامج Chrome. إذا كانت صفحتك تُجري عملية جمع وحذف للذاكرة بشكل متكرّر، من المرجّح أنّك تخصص الذاكرة بشكل متكرّر جدًا، ما يؤدي إلى استهلاك ذاكرة الجيل الجديد.
الخاتمة
لقد بدأنا في أزمة. تناولت أساسيات إدارة الذاكرة في JavaScript وV8 على وجه الخصوص. لقد تعرّفت على كيفية استخدام الأدوات، بما في ذلك ميزة تتبُّع الأجسام الجديدة المتوفّرة في أحدث إصدارات Chrome. وبفضل هذه المعرفة، تمكّن فريق Gmail من حلّ مشكلة استخدام الذاكرة وتحسين الأداء. يمكنك إجراء ذلك أيضًا مع تطبيقات الويب.