كيفية حفظ الذاكرة في علامات تبويب المتصفح ، ولكن لا تفقد محتوياتها. تجربة فريق Yandex.Browser

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

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



في مشروع Chromium ، يعانون من استهلاك الذاكرة لعلامات تبويب الخلفية من خلال محو ذاكرة التخزين المؤقت المختلفة. هذا لا يتعلق بالذاكرة المؤقتة التي يتم فيها تخزين صور الصفحات المحملة. لا توجد مشكلة معه - يعيش على القرص الصلب. في المتصفح الحديث ، هناك العديد من المعلومات المخزنة مؤقتًا المخزنة في ذاكرة الوصول العشوائي.

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

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

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

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

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

السبات في متصفح Yandex


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

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

يتحقق Yandex.Browser عدة مرات في الدقيقة من حجم الذاكرة المتاحة ، وإذا كانت أقل من قيمة العتبة البالغة 600 ميجابايت ، فسيتم تشغيل Hibernate. يبدأ كل شيء بحقيقة أن المتصفح يجد علامة التبويب الخلفية (للاستخدام) الأقدم. بالمناسبة ، لدى المستخدم العادي 7 علامات تبويب مفتوحة ، ولكن 5 ٪ لديها أكثر من 30 علامة تبويب.

لا يمكنك إلغاء تحميل أي علامة تبويب قديمة من الذاكرة - يمكنك كسر شيء مهم حقًا. على سبيل المثال ، تشغيل الموسيقى أو الدردشة في رسول ويب. يوجد 28 استثناءًا من هذا القبيل الآن. إذا لم تكن علامة التبويب مناسبة لواحد منها على الأقل ، فسيتابع المتصفح فحص علامة التبويب التالية.

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

حفظ علامات التبويب واستعادتها في الإسبات


يمكن تقسيم أي صفحة إلى جزأين كبيرين ، مرتبطين بالمحركات V8 (JS) و Blink (HTML / DOM). فكر في مثال صغير:

<html> <head> <script type="text/javascript"> function onLoad() { var div = document.createElement("div"); div.textContent = "Look ma, I can set div text"; document.body.appendChild(div); } </script> </head> <body onload="onLoad()"></body> </html> 


لدينا بعض شجرة DOM ونص صغير يضيف ببساطة div إلى الجسم. من وجهة نظر Blink ، تبدو هذه الصفحة شيئًا مثل هذا:



لنلق نظرة على العلاقة بين Blink و V8 باستخدام مثال HTMLBodyElement:



قد تلاحظ أن لكل من Blink و V8 تمثيلات مختلفة لنفس الكيانات وأنهما مرتبطان ببعضهما البعض بشكل وثيق. لذلك وصلنا إلى الفكرة الأصلية - لحفظ الحالة الكاملة لـ V8 ، ولتخزين Blink سمات HTML فقط في شكل نص. لكن هذا كان خطأ ، لأننا فقدنا حالات كائنات DOM التي لم يتم تخزينها في السمات. لقد فقدنا أيضًا حالات لم يتم تخزينها في DOM. كان الحل لهذه المشكلة هو إنقاذ Blink بالكامل. ولكن ليس بهذه البساطة.

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

إذا أحصينا جميع فئات Blink التي تمثل شجرة DOM ، بالإضافة إلى واجهات برمجة تطبيقات HTML5 مختلفة (على سبيل المثال ، قماش ، وسائط ، تحديد الموقع الجغرافي) ، فإننا نحصل على الآلاف من الفئات. يكاد يكون من المستحيل كتابة منطق حفظ جميع الفئات بيديك. لكن أسوأ جزء هو أنه حتى إذا فعلت ذلك ، فسيكون من المستحيل الحفاظ عليه ، لأننا نقوم بانتظام بتحديث إصدارات جديدة من Chromium تقوم بإجراء تغييرات غير متوقعة على أي فصل دراسي.

تم بناء متصفحنا لجميع المنصات باستخدام clang. لحل مشكلة الحفاظ على فئات Blink ، أنشأنا مكونًا إضافيًا لـ clang ، والذي ينشئ AST (شجرة بناء مجردة) للفصول. على سبيل المثال ، هذا الرمز:

رمز الفصل
 class Bar : public foo_namespace::Foo { struct BarInternal { int int_field_; float float_field_; } bar_internal_field_; std::string string_field_; }; 


يتحول إلى XML:

نتيجة البرنامج المساعد في XML
 <class> <name>bar_namespace::Bar::BarInternal</name> <is_union>false</is_union> <is_abstract>false</is_abstract> <decl_source_file>src/bar.h</decl_source_file> <base_class_names></base_class_names> <fields> <field> <name>int_field_</name> <type> <builtin> <is_const>0</is_const> <name>int</name> </builtin> </type> </field> <field> <name>float_field_</name> <type> <builtin> <is_const>0</is_const> <name>float</name> </builtin> </type> </field> </class> <class> <name>bar_namespace::Bar</name> <is_union>false</is_union> <is_abstract>false</is_abstract> <decl_source_file>src/bar.h</decl_source_file> <base_class_names> <class_name>foo_namespace::Foo</class_name> </base_class_names> <fields> <field> <name>bar_internal_field_</name> <type> <class> <is_const>0</is_const> <name>bar_namespace::Bar::BarInternal</name> </class> </type> </field> <field> <name>string_field_</name> <type> <class> <is_const>0</is_const> <name>std::string</name> </class> </type> </field> </fields> </class> 


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

C ++ حفظ التعليمات البرمجية التي تم الحصول عليها عن طريق البرنامج النصي من XML
 void serialize_bar_namespace_Bar_BarInternal( WriteVisitor* writer, Bar::BarInternal* instance) { writer->WriteBuiltin<size_t>(instance->int_vector_field_.size()); for (auto& item : instance->int_vector_field_) { writer->WriteBuiltin<int>(item); } writer->WriteBuiltin<float>(instance->float_field_); } void serialize_bar_namespace_Bar(WriteVisitor* writer, Bar* instance) { serialize_foo_namespace_Foo(writer, instance); serialize_bar_namespace_Bar_BarInternal( writer, &instance->bar_internal_field_); writer->WriteString(instance->string_field_); } 


في المجموع ، نقوم بإنشاء رمز لحوالي 1000 فئة من Blink. على سبيل المثال ، تعلمنا حفظ فئة معقدة مثل Canvas. يمكنك الرسم منه في كود JS ، وتعيين العديد من الخصائص ، وتعيين معلمات الفرشاة للرسم ، وما إلى ذلك. نحفظ كل هذه الخصائص والمعلمات والصورة نفسها.

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

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

سجلنا مقطع فيديو مع توضيح واضح لكيفية قيام Hibernate بإلغاء تحميل علامات التبويب من خلال النقر واستعادتها مع الحفاظ على التقدم في لعبة JS التي تم إدخالها في موضع النص والفيديو:


الملخص


في المستقبل القريب ، ستصبح تقنية Hibernate متاحة لجميع مستخدمي Yandex.Browser لنظام التشغيل Windows. نخطط أيضًا لبدء تجربتها في إصدار ألفا لنظام Android. مع ذلك ، يحفظ المتصفح الذاكرة بشكل أكثر كفاءة من ذي قبل. على سبيل المثال ، بالنسبة للمستخدمين الذين لديهم عدد كبير من علامات التبويب المفتوحة ، يحفظ Hibernate في المتوسط ​​أكثر من 330 ميغابايت من الذاكرة ولا يفقد المعلومات في علامات التبويب ، والتي تظل متاحة بنقرة واحدة في أي حالة شبكة. نحن نتفهم أنه سيكون من المفيد لمشرفي المواقع التفكير في إلغاء تحميل علامات تبويب الخلفية ، لذلك نخطط لدعم واجهة برمجة تطبيقات Page Lifecycle API .

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

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


All Articles