جمع القمامة ()

لتنفيذ جافا سكريبت ، يحتاج المستعرض إلى القليل من الذاكرة ، لكن في مكان ما تحتاج إلى تخزين كائنات أو بدائل أو وظائف يتم إنشاؤها لجميع إجراءات المستخدم. لذلك ، يخصص المستعرض أولاً الكمية المطلوبة من ذاكرة الوصول العشوائي ، وعندما لا يتم استخدام الكائنات ، فإنه ينظفها بشكل مستقل.

من الناحية النظرية ، يبدو جيدًا. في الممارسة العملية ، يفتح المستخدم 20 علامة تبويب من يوتيوب ، والشبكات الاجتماعية ، ويقرأ شيئا ، ويعمل ، والمستعرض يأكل الذاكرة ، مثل البنزين Hummer H2. جامع القمامة ، مثل هذا الوحش مع الممسحة ، يمر عبر الذاكرة بالكامل ويضيف البلبلة ، كل شيء يبطئ ويتعطل.



لمنع حدوث مثل هذه المواقف وعدم تعرض أداء مواقعنا وتطبيقاتنا للمعاناة ، يجب أن يعرف المطور الأمامي كيف تؤثر البيانات المهملة على التطبيقات وكيف يجمعها المستعرض ويحسن العمل مع الذاكرة وكيف يختلف كل شيء عن الواقع القاسي. هذا هو مجرد تقرير Andrei Roenko ( flapenguin ) في Frontend Conf 2018 .

نحن نستخدم أداة تجميع مجمعي القمامة (ليس في المنزل - في تطوير الواجهة الأمامية) كل يوم ، لكننا لا نفكر حقًا فيما هو عليه على الإطلاق ، وما يكلفنا وما هي الفرص والقيود التي لديه.

إذا كانت مجموعة البيانات المهملة تعمل بالفعل في JavaScript ، فستحذف معظم وحدات npm نفسها فورًا بعد التثبيت.

ولكن في حين أن هذا ليس كذلك ، وسوف نتحدث عن ما هو - حول تجميع الأشياء غير الضرورية.


نبذة عن المتحدث : اندريه رونكو يعمل على تطوير واجهة برمجة تطبيقات Yandex.Map ، وقد كان في الواجهة الأمامية لمدة ست سنوات حتى الآن ، ويحب أن يصمم أعماله التجريدية العالية وينحدر من أشخاص آخرين إلى الأرض.

لماذا تحتاج جمع القمامة؟


النظر في مثال Yandex.Maps. Yandex.Maps هي خدمة ضخمة ومعقدة تستخدم الكثير من JS وجميع واجهات برمجة التطبيقات الموجودة في المتصفح ، باستثناء الوسائط المتعددة ، ومتوسط ​​وقت الجلسة هو 5-10 دقائق. وفرة جافا سكريبت يخلق العديد من الكائنات. يؤدي سحب الخريطة وإضافة المؤسسات ونتائج البحث والعديد من الأحداث الأخرى التي تحدث كل ثانية إلى إنشاء سلسلة من الكائنات. أضف إلى هذا التفاعل ، والكائنات تصبح أكثر.

ومع ذلك ، تشغل كائنات JS 30-40 ميغابايت فقط على الخريطة. بالنسبة لجلسات Yandex.Maps الطويلة والتخصيص المستمر للكائنات الجديدة ، فإن هذا لا يكفي.

سبب الحجم الصغير للكائنات هو أنه يتم تجميعها بنجاح بواسطة أداة تجميع البيانات المهملة ويتم اعادة استخدام الذاكرة.

اليوم سنتحدث عن جمع القمامة من أربعة جوانب:

  • النظرية لنبدأ معها في التحدث بنفس اللغة وفهم بعضنا البعض.
  • حقيقة قاسية. في نهاية المطاف ، ينفذ الكمبيوتر رمز الجهاز الذي لا توجد فيه جميع التجريدات المألوفة بالنسبة لنا. دعنا نحاول معرفة كيفية عمل جمع القمامة بمستوى منخفض.
  • متصفح الواقع. دعونا نرى كيف يتم تطبيق جمع القمامة في المحركات والمتصفحات الحديثة ، وما هي الاستنتاجات التي يمكن استخلاصها من هذا.
  • الحياة اليومية - دعنا نتحدث عن التطبيق العملي للمعرفة المكتسبة في الحياة اليومية.

نحن ندعم جميع العبارات مع أمثلة على كيف يمكنك وكيف لا تحتاج إلى القيام بذلك.

لماذا تعرف كل هذا؟


جمع القمامة هو شيء غير مرئي بالنسبة لنا ، ومع ذلك ، ومعرفة كيف يتم ترتيب ذلك سوف:

  • لديك فكرة عن الأداة التي تستخدمها ، والتي هي مفيدة في عملك.
  • فهم مكان تحسين التطبيقات التي تم إصدارها بالفعل وكيفية تصميم التطبيقات المستقبلية بحيث تعمل بشكل أفضل وأسرع.
  • تعرف على كيفية عدم ارتكاب أخطاء شائعة والتوقف عن إهدار الموارد على "تحسينات" عديمة الفائدة وضارة.

النظرية


قال جويل سبولسكي ذات مرة:

جميع التجريدات غير التافهة متسربة.

جامع القمامة هو عبارة عن تجريد كبير غير تافه يتم تصحيحه من جميع الجوانب. لحسن الحظ ، فإنه يتدفق نادرا جدا.

لنبدأ بنظرية ، لكن بدون تعريفات مملة. دعنا نحلل عمل المجمع باستخدام رمز بسيط كمثال:

window.Foo = class Foo { constructor() { this.x = { y: 'y' }; } work(name) { let z = 'z'; return function () { console.log(name, this.xy, z); this.x = null; }.bind(this); } }; 

  • هناك فئة في الرمز.
  • الفصل لديه منشئ .
  • طريقة العمل تقوم بإرجاع دالة ذات صلة.
  • داخل الوظيفة ، يتم استخدام هذا واثنين من المتغيرات من الإغلاق.

لنرى كيف سيتصرف هذا الرمز إذا قمنا بتشغيله بهذه الطريقة:

 var foo = new Foo(); //C   window.worker = foo.work('Brendan Eich'); //     bind,   window.foo = null; //   window.Foo = null; //  ,   -  window.worker(); window.worker = null; //   ,   

دعنا نحلل الشفرة ومكوناتها بمزيد من التفصيل ونبدأ بالفصل.

إعلان فئة




يمكننا أن نفترض أن الفصول في ECMAScript 2015 هي مجرد سكر نحوي للوظائف. جميع الوظائف لها:

  • الوظيفة. [[النموذج الأولي]] هو النموذج الأولي الحقيقي للوظيفة.
  • Foo.prototype هو نموذج أولي للكائنات التي تم إنشاؤها حديثًا.
  • يحتوي Foo.prototype على رابط إلى المُنشئ من خلال حقل المُنشئ. هذا كائن ، لذلك يرث من Object.prototype .
  • طريقة العمل هي وظيفة منفصلة يوجد بها رابط ، على غرار المُنشئ ، لأنهما كليهما مجرد وظائف. يمكنه أيضًا تعيين نموذج أولي والاتصال به من خلال الجديد ، ولكن نادرًا ما يستخدم أي شخص هذا السلوك.

تشغل النماذج الأولية مساحة كبيرة على الدائرة ، لذلك دعونا نتذكر أنها موجودة ، ولكننا سنزيلها من أجل البساطة.

إنشاء كائن فئة




  • نضع صفنا في النافذة ، لأن الفصول لا تصل إلى هناك بشكل افتراضي.
  • إنشاء كائن فئة.
  • يؤدي إنشاء كائن تلقائيًا إلى الكشف عن النموذج الأولي لكائن الفئة في Foo.prototype. لذلك ، عند محاولة استدعاء طريقة العمل على كائن ما ، ستعرف نوع العمل الذي تقوم به.
  • يقوم مُنشئنا بإنشاء الحقل x في الكائن من الكائن باستخدام السلسلة.

إليك ما حدث:



تُرجع الطريقة وظيفة منضم - هذا كائن "سحري" خاص في JS ، والذي يتكون من منضم إلى هذه ووظيفة يجب استدعاؤها. تحتوي الوظيفة ذات الصلة أيضًا على نموذج أولي ونموذج آخر ، لكننا مهتمون بالإغلاق. حسب المواصفات ، يتم تخزين الإغلاق في البيئة. على الأرجح أنك أكثر دراية بكلمة Scope ، ولكن في المواصفات يسمى الحقل بالبيئة .



البيئة يخزن إشارة إلى LexicalEnvironment. هذا كائن معقد ، أكثر تعقيدًا من الشريحة ؛ فهو يخزن الروابط إلى كل شيء يمكن الوصول إليه من إحدى الوظائف. على سبيل المثال ، النافذة ، Foo ، الاسم ، و z. كما أنه يخزن روابط إلى ما لا تستخدمه بشكل صريح. على سبيل المثال ، يمكنك استخدام eval واستخدام الكائنات غير المستخدمة بطريق الخطأ ، ولكن يجب عدم كسر JS.

لذلك ، قمنا ببناء كل الأشياء والآن سوف ندمر كل شيء.

حذف الرابط إلى الكائن


لنبدأ بإزالة الرابط الخاص بالكائن ، يتم تمييز هذا الرابط في الرسم البياني باللون الأحمر.



نحذف ولا يحدث شيء ، لأنه من النافذة إلى الكائن يوجد مسار من خلال وظيفة منضمة .



هذا يدفعنا إلى خطأ نموذجي.

خطأ شائع - اشتراك منسي


 externalElement.addEventListener('click', () => { if (this.shouldDoSomethingOnClick) { this.doSomething(); } }) 

يحدث عند الاشتراك: استخدام هذا ، صراحة من خلال ربط أو من خلال وظائف السهم ؛ استخدام شيء في الختام. ثم نسيت إلغاء الاشتراك ، ويصبح عمر الكائن الخاص بك أو ما هو موجود في الدائرة هو نفس عمر الاشتراك. على سبيل المثال ، إذا كان هذا عنصر DOM لا تلمسه ، فمن المحتمل أن يكون هذا هو الوقت المحتمل حتى نهاية عمر الصفحة.

لحل هذه المشكلات:

  • إلغاء الاشتراك.
  • فكر في عمر الاشتراك ومن يمتلكه.
  • إذا لم تتمكن من إلغاء الاشتراك لسبب ما ، فقم بإلغاء الروابط (أيا كان = لاغٍ) ، أو قم بتنظيف جميع حقول الكائن. إذا تسرب جسمك ، فسيكون صغيرًا وليس مؤسفًا.
  • استخدم WeakMap ، وربما هذا سوف يساعد في بعض الحالات.

احذف مرجع الفصل


المضي قدما ومحاولة إزالة الرابط الأحمر تسليط الضوء على الطبقة.



نحن نحذف الرابط ولا يتغير شيء بالنسبة لنا. والسبب هو أن الفصل يمكن الوصول إليه من خلال BoundThis ، حيث يوجد رابط للنموذج الأولي ، وفي النموذج الأولي هناك رابط يعود إلى المُنشئ.

خطأ نموذجي العمل عديمة الفائدة


لماذا كل هذه المظاهرات ضرورية؟ لأن هناك جانبًا للمشكلة عندما يأخذ الناس النصيحة لإلغاء الروابط حرفيًا للغاية وإلغاء كل شيء بشكل عام.

 destroy() { this._x = null; this._y = null; //  10 this._foobar = null } 

هذه وظيفة لا قيمة لها. إذا كان الكائن يتكون فقط من إشارات إلى كائنات أخرى ولم تكن هناك موارد هناك ، فلا حاجة إلى إتلاف (). يكفي أن تفقد الإشارة إلى الكائن ، وسوف يموت من تلقاء نفسه.

لا يوجد نصيحة عالمية. عندما يكون ذلك ضروريًا ، قم بالإلغاء ، وعندما لا ، لا تُبطل. Zeroing ليس خطأ ، ولكن ببساطة عمل عديم الفائدة.

المضي قدما. استدعاء طريقة وظيفة منضم وسوف يزيل الرابط من [كائن فو] إلى [كائن كائن]. سيؤدي ذلك إلى ظهور الكائنات الموجودة في مستطيل أزرق في الرسم التخطيطي.



هذه الكائنات هي JS القمامة. انه سيكون رائعا. ومع ذلك ، هناك القمامة التي لا يمكن جمعها.

القمامة التي لن


في العديد من واجهات برمجة التطبيقات للمتصفح ، يمكنك إنشاء وتدمير كائن. إذا لم يتم تدمير الكائن ، فلن يتمكن أي جامع من تجميعه.

كائنات مع إنشاء / حذف وظائف الزوج:

  • createObjectURL () ، revokeObjectURL () ؛
  • WebGL: إنشاء / حذف Program / Shader / Buffer / Texture / إلخ ؛
  • ImageBitmap.close ()؛
  • indexDb.close ().

على سبيل المثال ، إذا نسيت حذف ObjectURL من فيديو بحجم 200 ميجابايت ، فستبقى هذه الملفات البالغ حجمها 200 ميجابايت في الذاكرة حتى نهاية عمر الصفحة وحتى أطول ، نظرًا لوجود تبادل للبيانات بين علامات التبويب. وبالمثل في WebGL و indexDb وغيرها من واجهات برمجة التطبيقات للمستعرض مع موارد مماثلة.

لحسن الحظ ، في مثالنا ، يحتوي المستطيل الأزرق على كائنات جافا سكريبت فقط ، لذلك هذا مجرد قمامة يمكن إزالتها.

الخطوة التالية هي مسح الرابط الأخير من اليسار إلى اليمين. هذا هو إشارة إلى الطريقة التي تلقيناها ، وظيفة ذات صلة.



بعد إزالته ، لن يكون لدينا روابط اليسار واليمين؟ في الواقع ، لا تزال هناك روابط من الإغلاق.



من المهم عدم وجود روابط من اليسار إلى اليمين ، لذلك كل شيء ما عدا النافذة هو القمامة وسوف يموت.

ملاحظة مهمة : هناك مراجع دائرية في البيانات المهملة ، مثل الكائنات التي تشير إلى بعضها البعض. لا يؤثر وجود هذه الارتباطات على أي شيء ، لأن أداة تجميع مجمعي البيانات المهملة لا تجمع كائنات فردية ، ولكنها تجمع البيانات المهملة بأكملها.



لقد ألقينا نظرة على الأمثلة ، والآن على مستوى الحدس ، نفهم ماهية القمامة ، ولكن دعونا نقدم تعريفًا كاملاً للمفهوم.

القمامة هي كل شيء ليس كائنًا حيًا.

كل شيء أصبح واضحا جدا. ولكن ما هو كائن حي؟

الكائن الحي هو كائن يمكن الوصول إليه بواسطة ارتباطات من الكائن الجذر.

يظهر مفهومان جديدان: "اتبع الروابط" و "كائن الجذر". أحد الكائنات الجذرية التي نعرفها بالفعل هو النافذة ، لذلك دعونا نبدأ بالارتباطات.

ماذا يعني اتباع الروابط؟


هناك العديد من الكائنات المرتبطة ببعضها البعض وتشير إلى بعضها البعض. سوف نلوح على طولهم ، بدءًا من كائن الجذر.

نقوم بتهيئة الخطوة الأولى ، ومن ثم المتابعة وفقًا للخوارزمية التالية: دعنا نقول أن كل شيء على قمة الموجة هو كائنات حية ونرى ما يشيرون إليه.



نحن تهيئة الخطوة الأولى. بعد ذلك سنتصرف وفقًا للخوارزمية التالية: دعنا نقول أن كل شيء أصفر على قمة الموجة هو كائنات حية ودعونا نرى ما يشيرون إليه.

ما يشيرون إليه ، سننشئ قمة جديدة للموجة:



انتهى وابدأ من جديد:

  • نحن احياء.
  • نحن ننظر إلى ما يشيرون إليه.
  • إنشاء قمة موجة جديدة ، تحريك الكائنات.
  • نحن ننظر إلى ما يشيرون إليه.



مع ملاحظة أن سهمًا واحدًا يشير إلى كائن حي بالفعل ، فإننا ببساطة لا نفعل شيئًا. كذلك وفقا للخوارزمية ، حتى يتم نفاد الكائنات التي تدور حولها. ثم نقول أننا وجدنا كل الكائنات الحية ، وكل شيء آخر هو القمامة.



وتسمى هذه العملية وضع العلامات .

ماذا يعني كائن الجذر؟



  • نافذة
  • تقريبا جميع واجهات برمجة التطبيقات المتصفح.
  • كل الوعد.
  • كل ما يتم وضعه في Microtask و Macrotask.
  • مراقبو الطفرة ، سلاح الجو الملكي البريطاني ، عمليات الخمول. لا يمكن حذف كل ما يمكن الوصول إليه من ما يكمن في RAF ، لأنه إذا قمت بحذف الكائن المستخدم في RAF ، فمن المحتمل أن يحدث خطأ ما.

التجمع يمكن أن يحدث في أي وقت. في كل مرة تظهر فيها الأقواس أو الوظائف ، يتم إنشاء كائن جديد. قد لا تكون هناك ذاكرة كافية ، وسيبحث المجمّع مجانًا:

 function foo (a, b, c) { function bar (x, y, z) { const x = {}; // nomem, run gc D: // … } while (whatever()) bar(); } 

في هذه الحالة ، ستكون الكائنات الجذرية هي كل شيء في مكدس الاستدعاءات. إذا توقفت ، على سبيل المثال ، عند السطر X وحذف ما يشير إليه Y ، فسوف يتعطل تطبيقك. JS لا تسمح لنا بمثل هذه التفاهات ، لذلك لا يمكنك حذف كائن من Y.

إذا بدا الجزء السابق معقدًا ، فسيكون الأمر أكثر صعوبة.

حقيقة قاسية


دعونا نتحدث عن عالم الآلات التي نتعامل فيها مع الحديد ، مع الوسائط المادية.

الذاكرة عبارة عن صفيف واحد كبير تكمن فيه الأرقام فقط ، على سبيل المثال: Uint32Array الجديد (16 * 2 ** 30).

لنقم بإنشاء كائنات في الذاكرة وإضافتها من اليسار إلى اليمين. نخلق واحدًا ، ثانيًا ، ثالثًا - جميعها بأحجام مختلفة. نضع الروابط على طول الطريق.


في الكائن السابع ، انتهى المكان ، لأن لدينا مربعين مجانين ، لكننا بحاجة إلى 5.

ما الذي يمكن عمله هنا؟ الخيار الأول هو تعطل. في الفناء في عام 2018 ، لدى كل شخص أحدث أجهزة MacBooks و 16 جيجابايت من ذاكرة الوصول العشوائي. لا توجد حالات عندما لا توجد ذاكرة!

ومع ذلك ، فإن ترك الأمور تسير في مسارها فكرة سيئة ، لأن هذا على الويب يؤدي إلى شاشة مشابهة:



ليس هذا هو السلوك الذي نريده من البرنامج ، ولكنه بشكل عام صحيح. هناك فئة من هواة الجمع تسمى No-op .

جامع لا المرجع


الايجابيات:

  • جامع بسيط جدا.
  • ببساطة لا يوجد جمع للقمامة.
  • لا حاجة للكتابة أو التفكير في الذاكرة.

سلبيات:

  • كل شيء يسقط بحيث لا يرتفع مرة أخرى.

بالنسبة للواجهة الأمامية ، جامع no-op غير ذي صلة ، ولكن يتم استخدامه في الواجهة الخلفية. على سبيل المثال ، بوجود عدة خوادم خلف الموازنات ، يتم إعطاء التطبيق 32 جيجابايت من ذاكرة الوصول العشوائي ومن ثم يتم قتله بالكامل. إنه أبسط ويتم تحسين الأداء فقط بمجرد إعادة التشغيل عندما تنخفض الذاكرة.

من المستحيل على الويب وعليك تنظيفه.

بحث وحذف القمامة


نبدأ التنظيف مع القمامة. نحن نعرف بالفعل كيف نفعل ذلك. Garbage - الكائنات C و F في المخطط السابق ، لأنه لا يمكنك الوصول إليها على طول الأسهم من الكائن الجذر.

نحن نأخذ هذه القمامة ، ونطعمها إلى محب القمامة وقد انتهيت.



بعد التنظيف ، لا يتم حل المشكلة ، حيث تبقى الثقوب في الذاكرة. يرجى ملاحظة أن هناك 7 مربعات مجانية ، لكن 5 منها لا يزال يتعذر علينا تخصيصها. حدث تجزئة وكان التجميع قد انتهى. وتسمى هذه الخوارزمية مع الثقوب مارك والكنس .

مارك واكتساح


الايجابيات:

  • خوارزمية بسيطة جدا. واحد من أول من ستتعلمه إذا بدأت بالتعرف على أداة تجميع مجمعي البيانات المهملة.
  • وهو يعمل بما يتناسب مع كمية القمامة ، ولكن يتواءم فقط عندما يكون هناك القليل من القمامة.
  • إذا كان لديك كائنات حية فقط ، فهو لا يضيع الوقت ولا يفعل شيئًا.

سلبيات:

  • يتطلب الأمر منطقًا معقدًا للبحث عن مساحة خالية ، لأنه عندما يكون هناك الكثير من الثقوب في الذاكرة ، يجب عليك تجربة كائن في كل منها لفهم ما إذا كان مناسبًا أم لا.
  • شظايا الذاكرة. قد يحدث موقف أنه مع 200 ميجابايت مجانًا ، يتم تقسيم الذاكرة إلى أجزاء صغيرة ، وكما هو موضح في المثال أعلاه ، لا توجد قطعة صلبة من الذاكرة للكائن.

نحن نبحث عن أفكار أخرى. إذا نظرت إلى الصورة وفكرت ، فإن الفكرة الأولى هي تحويل كل شيء إلى اليسار. ثم على اليمين ، ستكون هناك قطعة واحدة كبيرة ومجانية ، والتي سيتناسب بها جسمنا بهدوء.

هناك مثل هذه الخوارزمية ويسمى مارك والضغط .

مارك والمدمجة


الايجابيات:

  • ذاكرة التجزئة.
  • إنه يعمل بالتناسب مع عدد الكائنات الحية ، مما يعني أنه يمكن استخدامه عندما لا يكون هناك أي حطام عمليًا.

سلبيات:

  • صعوبة في العمل والتنفيذ.
  • ينقل الأشياء. لقد قمنا بنقل الكائن ونسخه ، والآن أصبح في مكان مختلف وكانت العملية بأكملها باهظة الثمن.
  • يتطلب 2-3 تمريرات عبر الذاكرة ، اعتمادًا على التنفيذ - الخوارزمية بطيئة.

هنا نأتي إلى فكرة أخرى.

جمع القمامة ليست مجانية


في واجهات برمجة التطبيقات عالية الأداء مثل WebGL و WebAudio و WebGPU ، التي لا تزال قيد التطوير ، يتم إنشاء الكائنات وحذفها في مراحل منفصلة. تتم كتابة هذه المواصفات بحيث لا يتم تجميع البيانات المهملة. علاوة على ذلك ، لا يوجد حتى وعد ، ولكن سحب () - أنت فقط تسأل كل إطار: "هل حدث شيء أم لا؟".

نصف الكرة الملقب ليسب 2


هناك جامع آخر أريد أن أتحدث عنه. ماذا لو لم تحرر الذاكرة ، لكن انسخ كل الكائنات الحية إلى مكان آخر.

دعونا نحاول نسخ كائن الجذر "كما هو" ، والذي يشير إلى مكان ما.



ثم الجميع.



لا توجد حطام أو ثقوب في الذاكرة أعلاه. يبدو أن كل شيء على ما يرام ، ولكن تنشأ مشكلتان:

  • كائنات مكررة - لدينا اثنين من الكائنات الخضراء واثنين من الكائنات الزرقاء. أي واحد للاستخدام؟
  • تؤدي الارتباطات من كائنات جديدة إلى كائنات قديمة وليس لبعضها البعض.

باستخدام الارتباطات ، يتم حل كل شيء بمساعدة "سحر" حسابي خاص ، ويمكننا التعامل مع ازدواجية الكائنات بحذف كل شيء أدناه.


نتيجة لذلك ، لدينا مساحة حرة ، والكائنات الحية فقط بالترتيب الطبيعي أعلاه. تسمى هذه الخوارزمية Semispace ، Lisp 2 ، أو ببساطة "جامع النسخ".

الايجابيات:

  • ذاكرة التجزئة.
  • بسيط
  • يمكن دمجها مع مرحلة الالتفافية.
  • وهو يعمل بما يتناسب مع عدد الكائنات الحية مع مرور الوقت.
  • يعمل بشكل جيد عندما يكون هناك الكثير من القمامة. إذا كان لديك 2 غيغابايت من الذاكرة و 3 كائنات فيه ، فستتجاوز 3 كائنات فقط ، ويبدو أن 2 غيغابايت المتبقية قد ولت.

سلبيات:

  • ضعف استهلاك الذاكرة. يمكنك استخدام الذاكرة 2 مرات أكثر من اللازم.
  • الأجسام المتحركة هي أيضًا ليست عملية رخيصة جدًا.

ملاحظة: يمكن لهواة جمع القمامة نقل الكائنات.

على الويب ، هذا غير ذي صلة ، ولكن على Node.js كثيرًا. إذا كتبت الامتداد في C ++ ، فإن اللغة لا تعرف كل هذا ، لذلك هناك روابط مزدوجة تسمى المقبض وتبدو مثل هذا: v8 :: Local <v8 :: String>.

لذلك ، إذا كنت ستكتب مكونات إضافية لـ Node.js ، فستكون المعلومات في متناول يديك.

نلخص الخوارزميات المختلفة مع إيجابيات وسلبيات في الجدول. كما أن لديها خوارزمية عدن ، ولكن عن ذلك في وقت لاحق.



أريد حقًا خوارزمية بدون سلبيات ، لكن هذا ليس كذلك. لذلك ، نحن نأخذ أفضل ما في العالمين: نستخدم عدة خوارزميات في نفس الوقت. في قطعة واحدة من الذاكرة ، نجمع القمامة مع خوارزمية واحدة ، وفي أخرى مع خوارزمية أخرى.

كيف نفهم فعالية الخوارزمية في مثل هذا الموقف؟

يمكننا استخدام معرفة الأزواج الأذكياء من الستينيات الذين نظروا إلى جميع البرامج وأدركوا:

فرضية الأجيال ضعيفة: معظم الأشياء تموت الشباب.

أرادوا أن يقولوا أن جميع البرامج تقوم فقط بإنتاج القمامة. في محاولة لاستخدام المعرفة ، سنتوصل إلى ما يسمى "التجميع عبر الأجيال".

تجميع الأجيال


نخلق جزأين من الذاكرة غير متصلتين بأي شكل من الأشكال: على اليسار هو Eden ، وعلى اليمين يكون Mark و Sweep بطيئًا. في عدن نخلق الأشياء. الكثير من الأشياء.



عندما يقول عدن إنه ممتلئ ، نبدأ في جمع القمامة فيه. نجد الكائنات الحية ونسخها إلى جامع آخر.



يتم تنظيف عدن نفسها بالكامل ، ويمكننا إضافة المزيد من الأشياء إليها.



بالاعتماد على فرضية الأجيال ، قررنا أن الكائنات c ، g ، من المرجح أن أعيش لفترة طويلة ، ويمكنك التحقق منها بحثًا عن القمامة في كثير من الأحيان. معرفة هذه الفرضية ، يمكنك كتابة البرامج التي تخدع جامع. يمكن القيام بذلك ، لكنني لا أنصحكم ، لأنه سيؤدي دائمًا إلى تأثيرات غير مرغوب فيها. إذا قمت بإنشاء سلة مهملات طويلة العمر ، فسوف يبدأ المجمع في تصديق أنه لا يحتاج إلى تجميعها.

مثال كلاسيكي للغش هو LRU-cache. يوجد كائن في ذاكرة التخزين المؤقت لفترة طويلة ، ينظر إليه المجمّع ويعتقد أنه لن يجمعه بعد ، لأن الكائن سيعيش لفترة طويلة جدًا. ثم يدخل كائن جديد في ذاكرة التخزين المؤقت ، ويتم إخراج كائن قديم كبير منه ولم يعد من الممكن تجميع هذا الكائن الكبير على الفور.

كيف نجمع الآن نحن نعرف. تحدث عن موعد التجميع.

عندما لجمع؟


الخيار الأسهل هو عندما نوقف كل شيء ، ونبدأ الإنشاء ، ثم نبدأ العمل في JS مرة أخرى.



في أجهزة الكمبيوتر الحديثة ، أكثر من موضوع التنفيذ. على الويب ، هذا مألوف لدى "عمال الويب". لماذا لا تأخذ وموازنة عملية التجميع . سيكون أداء العديد من العمليات الصغيرة في نفس الوقت أسرع من عملية واحدة كبيرة.



هناك فكرة أخرى تتمثل في عمل لقطة سريعة للحالة الحالية ، والبناء بالتوازي مع JS .



إذا كان هذا يهمك ، فإنني أنصحك بقراءة:

  • كتاب التجميع الوحيد والرئيسي ، Garbage Collection Handbook.
  • ويكيبيديا كمورد عالمي.
  • موقع memorymanagement.org.
  • تقارير ومقالات كتبها الكسندر شيبيليف . يتحدث عن Java ، ولكن فيما يتعلق بالقمامة ، يعمل Java و V8 بنفس الطريقة تقريبًا.

متصفح الواقع


دعنا ننتقل إلى كيفية استخدام المتصفحات لكل ما تحدثنا عنه.

محركات إنترنت الأشياء


لنبدأ مع المتصفحات ، ولكن مع محركات Internet of Things: JerryScript و Duktape. يستخدمون خوارزميات Mark'n'sweep و Stop the world.

تعمل محركات إنترنت الأشياء على ميكروكنترولر ، مما يعني: اللغة بطيئة ؛ توقف الثانية. تجزئة وكل هذا للحصول على إبريق الشاي مع الإضاءة :)

إذا كتبت Internet of Things في JavaScript ، فأخبرنا في التعليقات؟ هل هناك أي نقطة

سنترك محركات إنترنت الأشياء وحدها ، ونحن مهتمون بما يلي:

  • V8.
  • SpiderMonkey في الواقع ، ليس لديه شعار. شعار محلية الصنع :)
  • JavaScriptCore المستخدمة من قبل WebKit.
  • ChakraCore الذي يستخدم في الحافة.



جميع المحركات متشابهة تقريبًا ، لذلك سنتحدث عن الإصدار 8 ، باعتباره الأكثر شهرة.

V8


  • تقريبا جميع جافا سكريبت من جانب الخادم ، لأنه Node.js.
  • ما يقرب من 80 ٪ من جافا سكريبت من جانب العميل.
  • أكثر المطورين اجتماعيين ، هناك الكثير من المعلومات ورموز المصدر الجيدة التي يسهل قراءتها.

يستخدم V8 تجميع الأجيال.


الفرق الوحيد هو أننا اعتدنا أن يكون لدينا جامعان ، والآن ثلاثة:

  • يتم إنشاء كائن في عدن.
  • في مرحلة ما في عدن ، هناك الكثير من القمامة ويتم نقل الكائن إلى Semispace.
  • الكائن صغير السن وعندما يدرك المجمع أنه قديم وممل ، فإنه يلقي به في Mark و Sweep ، حيث يكون جمع القمامة نادرًا للغاية.

يمكنك أن ترى بوضوح كيف يبدو على تتبع الذاكرة .



عدة موجات كبيرة مع موجات صغيرة ملحوظة. المجموعات الصغيرة عبارة عن تجمعات صغيرة ، والكبيرة منها تجمعات كبيرة.

معنى وجودنا ، وفقًا لفرضية الأجيال ، هو توليد القمامة ، وبالتالي فإن الخطأ التالي هو الخوف من إنشاء القمامة.

يمكن إنشاء سلة المهملات عندما تكون سلة مهملات حقًا. , , , .

mark


V8 .


Stop the world, , JS, .

?


1 3%, .

3% = 1/33 GameDev. GameDev 3% 1 , . GameDev .

 const pool = [new Bullet(), new Bullet(), /* ... */]; function getFromPool() { const bullet = pool.find(x => !x.inUse); bullet.isUse = true; return bullet; } function returnToPool(bullet) { bullet.inUse = false; } // Frame const bullet = getFromPool(); // ... returnToPool(bullet); 

, , 10 000 .

— . , . , .

: Chromium


, , , Chromium.

 > performance.memory MemoryInfo { totalJSHeapSize: 10000000, usedJSHeapSize: 10000000, jsHeapSizeLimit: 2330000000 } 

Chromium performance.memory , , Chromium .

: Chromium 2 JavaScript.

, .

: Node


Node.js process.memoryUsage , .

 > process.memoryUsage() { rss: 22839296, heapTotal: 10207232, heapUsed: 5967968, external: 12829 } 

, - , . . .



— , . proposal , .

Node.js, c node-weak , , .

 let cached = new WeakRef(myJson); // 2   let json = cached.deref(); if (!json) { json = await fetchAgain(); } 

, , - JS. , , , .

WebAssembly , . , , , .

: v8.dev JS.


?



DevTools : Performance Memory . Chromium, , Firefox Safari .

Performance


Trace, «Memory» Performance, JS .



JS V8 , . . , GC 30 1200 JS, 1/40.

Memory


.



.



, . , , , V8 , . , .

, , Q ( compiled code) — React . , ?

, , , .

, .



, , , . , — 4 . , .



React, - : . , JSX.

Performance Memory , :

  • Chromium: about:tracing.
  • Firefox: about:memory about:performance, .
  • Node — trace-gc, —expose-gc, require('trace_events'). trace_events .

ملخص


  • , , , .
  • .
  • . , ?
  • , - .
  • SPA, , 1 , .
  • , - .

: flapenguin.me , Twitter , GitHub .

- ++ . YouTube-
.

, 2018 , . Frontend Conf 2018.

, :)

Source: https://habr.com/ru/post/ar433318/


All Articles