Lorsque les navigateurs manquent de mémoire, ils en déchargent les onglets les plus anciens. C'est ennuyeux car cliquer sur un tel onglet force le rechargement d'une page. Aujourd'hui, nous expliquerons aux lecteurs de Habr comment l'équipe Yandex.Browser résout ce problème en utilisant la technologie Hibernate.
Les navigateurs à base de chrome créent un processus pour chaque onglet. Cette approche présente de nombreux avantages. Il s'agit de la sécurité (isolement des sites les uns des autres), de la stabilité (le plantage d'un processus ne fait pas glisser tout le navigateur) et l'accélération du travail sur les processeurs modernes avec un grand nombre de cœurs. Mais il y a aussi un inconvénient - une consommation de mémoire plus élevée que lors de l'utilisation d'un processus pour tout. Si les navigateurs ne faisaient rien avec cela, leurs utilisateurs verraient constamment quelque chose comme ceci:

Dans le projet Chromium, ils luttent avec la consommation de mémoire des onglets d'arrière-plan en effaçant divers caches. Il ne s'agit pas du cache dans lequel les images des pages chargées sont stockées. Il n'y a aucun problème avec lui - il vit sur le disque dur. Dans un navigateur moderne, de nombreuses autres informations mises en cache sont stockées dans la RAM.
De plus, Chromium travaille depuis un certain temps pour arrêter les temporisateurs JS dans les onglets d'arrière-plan. Sinon, vider les caches n'a aucun sens, car les activités des onglets d'arrière-plan les restaurent. On pense que si les sites veulent travailler en arrière-plan, vous devez utiliser un technicien de service, pas des minuteries.
Si ces mesures ne vous aident pas, il ne reste qu'une chose: décharger le processus de rendu des onglets de la mémoire. Un site ouvert cesse simplement d'exister. Si vous basculez vers l'onglet, le téléchargement commence à partir du réseau. S'il y avait une vidéo de pause dans l'onglet, la lecture commencera depuis le début. Si le formulaire a été rempli sur la page, les informations saisies peuvent être perdues. Si une application JS lourde fonctionnait dans l'onglet, vous devrez la redémarrer.
Le problème du déchargement des onglets est particulièrement désagréable lorsqu'il n'y a pas d'accès au réseau. Avez-vous reporté un onglet avec Habr pour lire à bord d'un avion? Soyez prêt qu'un article utile deviendra une citrouille.
Les développeurs de navigateurs comprennent que cette mesure extrême est agaçante pour les utilisateurs (il suffit de se
tourner vers la recherche pour estimer l'étendue), ils l'appliquent donc au dernier moment. À l'heure actuelle, l'ordinateur ralentit déjà en raison d'un manque de mémoire, les utilisateurs le remarquent et recherchent d'autres moyens de résoudre le problème.Par exemple, l'extension
The Great Suspender compte plus de 1,4 million d'utilisateurs.
Les gens veulent que les navigateurs et la mémoire soient enregistrés, et ils ne commencent pas à ralentir. Pour ce faire, les onglets ne doivent pas être déchargés au dernier moment, mais un peu plus tôt. Et pour cela, vous devez arrêter de perdre le contenu des onglets, c'est-à-dire rendre le processus d'économie invisible. Mais alors, sur quoi économiser? Le cercle est fermé. Mais une solution a été trouvée.
Hibernation sur le navigateur Yandex
De nombreux lecteurs Habr pourraient déjà deviner quoi effacer la mémoire, mais enregistrer l'état de l'onglet est tout à fait possible si vous déchargez d'abord l'état sur le disque dur. Si vous cliquez sur un onglet pour restaurer l'onglet à partir du disque dur, l'utilisateur ne remarquera rien.
Notre équipe est impliquée dans le développement du projet Chromium, où il envoie d'importantes
modifications d'optimisation et de nouvelles
fonctionnalités . En 2015, nous avons
discuté avec des collègues du projet de l'idée de maintenir l'état des onglets sur le disque dur et avons même réussi à apporter un certain nombre d'améliorations, mais ils ont décidé de geler cette direction dans Chromium. Nous avons décidé différemment et avons poursuivi le développement de Yandex.Browser. Cela a pris plus de temps que prévu, mais cela en valait la peine. Ci-dessous, nous parlerons du bourrage technique de la technologie Hibernate, mais pour l'instant, commençons par la logique générale.
Plusieurs fois par minute, Yandex.Browser vérifie la quantité de mémoire disponible, et si elle est inférieure à la valeur seuil de 600 mégaoctets, Hibernate entre en jeu. Tout commence par le fait que le navigateur trouve l'onglet d'arrière-plan le plus ancien (à utiliser). Soit dit en passant, l'utilisateur moyen a 7 onglets ouverts, mais 5% en ont plus de 30.
Vous ne pouvez pas décharger un ancien onglet de la mémoire - vous pouvez casser quelque chose de vraiment important. Par exemple, jouer de la musique ou discuter dans un messager Web. Il existe actuellement 28 exceptions de ce type. Si l'onglet ne correspondait pas à au moins l'une d'entre elles, le navigateur passe à la vérification suivante.
Si un onglet est trouvé qui répond aux exigences, le processus de sauvegarde commence.
Enregistrement et restauration d'onglets dans Hibernate
Toute page peut être divisée en deux grandes parties, associées aux moteurs V8 (JS) et Blink (HTML / DOM). Prenons un petit exemple:
<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>
Nous avons un arbre DOM et un petit script qui ajoute simplement un div au corps. Du point de vue de Blink, cette page ressemble à ceci:

Examinons la relation entre Blink et V8 à l'aide de l'exemple HTMLBodyElement:

Vous pouvez remarquer que Blink et V8 ont des représentations différentes des mêmes entités et sont étroitement liés les uns aux autres. Nous sommes donc arrivés à l'idée originale: enregistrer l'état complet de la V8 et que Blink ne stocke que les attributs HTML sous forme de texte. Mais c'était une erreur, car nous avons perdu ces états d'objets DOM qui n'étaient pas stockés dans des attributs. Nous avons également perdu des états qui n'étaient pas stockés dans le DOM. La solution à ce problème était de sauver complètement Blink. Mais pas si simple.
Vous devez d'abord collecter des informations sur les objets Blink. Par conséquent, au moment de l'enregistrement de la V8, nous arrêtons non seulement JS et le castons, mais nous collectons également des références aux objets DOM et autres objets auxiliaires disponibles pour JS en mémoire. Nous parcourons également tous les objets accessibles depuis les objets Document - les éléments racine de chaque cadre de page. Nous collectons donc des informations sur tout ce qui est important à préserver. La partie la plus difficile est d'apprendre à économiser.
Si nous comptons toutes les classes Blink qui représentent l'arborescence DOM, ainsi que différentes API HTML5 (par exemple, canvas, media, géolocalisation), nous obtenons des milliers de classes. Il est presque impossible d'écrire la logique de sauvegarde de toutes les classes avec vos mains. Mais le pire, c'est que même si vous faites cela, il sera impossible à maintenir, car nous mettons régulièrement à jour de nouvelles versions de Chromium qui apportent des modifications inattendues à n'importe quelle classe.
Notre navigateur pour toutes les plateformes est construit en utilisant clang. Pour résoudre le problème de la préservation des classes Blink, nous avons créé un plugin pour clang, qui construit un AST (arbre de syntaxe abstraite) pour les classes. Par exemple, ce code:
Code de classe class Bar : public foo_namespace::Foo { struct BarInternal { int int_field_; float float_field_; } bar_internal_field_; std::string string_field_; };
Il se transforme en un tel XML:
Le résultat du plugin en 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>
De plus, d'autres scripts écrits par nous génèrent du code C ++ à partir de ces informations pour enregistrer et restaurer des classes, qui tombe dans l'assembly Yandex.Browser.
Code de sauvegarde C ++ obtenu par script à partir de 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_); }
Au total, nous générons du code pour environ 1000 classes Blink. Par exemple, nous avons appris à enregistrer une classe aussi complexe que Canvas. Vous pouvez en dessiner dans le code JS, définir de nombreuses propriétés, définir des paramètres de pinceau pour le dessin, etc. Nous sauvegardons toutes ces propriétés, paramètres et l'image elle-même.
Après avoir correctement chiffré et enregistré toutes les données sur le disque dur, le processus de tabulation est déchargé de la mémoire jusqu'à ce que l'utilisateur revienne à cet onglet. Dans l'interface, comme précédemment, il ne se démarque pas.
La récupération des onglets n'est pas instantanée, mais beaucoup plus rapide que lors du téléchargement depuis le réseau. Néanmoins, nous avons fait un geste délicat afin de ne pas déranger les utilisateurs avec des flashs d'un écran blanc. Nous montrons une capture d'écran de la page créée pendant la phase de sauvegarde. Cela permet de faciliter la transition. Sinon, le processus de récupération est similaire à la navigation normale, la seule différence étant que le navigateur ne fait pas de demande réseau. Il recrée la structure de trame et les arborescences DOM qu'ils contiennent, puis remplace l'état de V8.
Nous avons enregistré une vidéo avec une démonstration claire de la façon dont Hibernate décharge et restaure les onglets de clic tout en préservant la progression dans le jeu JS saisie dans la position du texte et de la vidéo:
Résumé
Dans un avenir proche, la technologie Hibernate sera disponible pour tous les utilisateurs de Yandex.Browser pour Windows. Nous prévoyons également de commencer à l'expérimenter dans la version alpha pour Android. Avec lui, le navigateur économise la mémoire plus efficacement qu'auparavant. Par exemple, pour les utilisateurs avec un grand nombre d'onglets ouverts, Hibernate enregistre en moyenne plus de 330 mégaoctets de mémoire et ne perd pas d'informations dans les onglets, qui restent accessibles en un clic dans n'importe quelle condition de réseau. Nous comprenons qu'il serait utile que les webmasters envisagent de décharger les onglets d'arrière-plan, nous prévoyons donc de prendre en charge l'
API Page Lifecycle .
Hibernate n'est pas notre seule solution visant à économiser des ressources. Ce n'est pas la première année que nous travaillons pour nous assurer que le navigateur s'adapte aux ressources disponibles dans le système. Par exemple, sur les appareils faibles, le navigateur passe en mode simplifié et lorsque l'ordinateur portable est déconnecté de la source d'alimentation, il réduit la consommation d'énergie. Économiser des ressources est une histoire grande et compliquée, sur laquelle nous reviendrons certainement à Habré.