Wenn den Browsern der Speicherplatz ausgeht, werden die ältesten Registerkarten daraus entladen. Dies ist ärgerlich, da durch Klicken auf eine solche Registerkarte ein erneutes Laden der Seite erzwungen wird. Heute werden wir den Lesern von Habr erzählen, wie das Yandex.Browser-Team dieses Problem mithilfe der Hibernate-Technologie löst.
Chromium-basierte Browser erstellen für jede Registerkarte einen Prozess. Dieser Ansatz hat viele Vorteile. Dies ist Sicherheit (Isolierung von Sites voneinander), Stabilität (Absturz eines Prozesses zieht nicht den gesamten Browser in Mitleidenschaft ziehen) und Beschleunigung der Arbeit auf modernen Prozessoren mit einer großen Anzahl von Kernen. Es gibt aber auch ein Minus - einen höheren Speicherverbrauch als bei Verwendung eines Prozesses für alles. Wenn Browser nichts damit anfangen würden, würden ihre Benutzer ständig so etwas sehen:

Im Chromium-Projekt haben sie Probleme mit dem Speicherverbrauch von Hintergrundregistern, indem sie verschiedene Caches löschen. Hier geht es nicht um den Cache, in dem die Bilder der geladenen Seiten gespeichert sind. Es gibt kein Problem mit ihm - er lebt auf der Festplatte. In einem modernen Browser gibt es viele andere zwischengespeicherte Informationen, die im RAM gespeichert sind.
Außerdem arbeitet Chromium seit einiger Zeit daran, JS-Timer in Hintergrundregistern zu stoppen. Andernfalls macht das Löschen der Caches keinen Sinn, weil Aktivitäten in den Hintergrundregistern stellen sie wieder her. Es wird davon ausgegangen, dass Sie, wenn Websites im Hintergrund arbeiten möchten, einen Servicemitarbeiter und keine Timer verwenden müssen.
Wenn diese Maßnahmen nicht helfen, bleibt nur noch eines übrig: das Entladen des gesamten Tab-Rendering-Prozesses aus dem Speicher. Eine offene Site hört einfach auf zu existieren. Wenn Sie zur Registerkarte wechseln, wird der Download aus dem Netzwerk gestartet. Wenn sich auf der Registerkarte ein Pausenvideo befand, wird es von Anfang an abgespielt. Wenn das Formular auf der Seite ausgefüllt wurde, können die eingegebenen Informationen verloren gehen. Wenn eine schwere JS-Anwendung auf der Registerkarte funktioniert hat, müssen Sie sie erneut starten.
Das Problem des Entladens von Registerkarten ist besonders unangenehm, wenn kein Zugriff auf das Netzwerk besteht. Haben Sie einen Tab mit Habr verschoben, um an Bord eines Flugzeugs zu lesen? Seien Sie darauf vorbereitet, dass aus einem nützlichen Artikel ein Kürbis wird.
Browser-Entwickler wissen, dass diese extreme Maßnahme für Benutzer ärgerlich ist (
wenden Sie sich einfach
an die Suche , um das Ausmaß abzuschätzen), und wenden sie daher im letzten Moment an. In diesem Moment verlangsamt sich der Computer bereits aufgrund von Speichermangel. Benutzer bemerken dies und suchen nach alternativen Möglichkeiten, um das Problem zu lösen. Daher hat beispielsweise
die Erweiterung
The Great Suspender mehr als 1,4 Millionen Benutzer.
Die Benutzer möchten, dass Browser und Speicher gespeichert werden, und sie werden nicht langsamer. Zu diesem Zweck sollten die Registerkarten nicht im letzten Moment, sondern etwas früher entladen werden. Und dafür müssen Sie aufhören, den Inhalt der Registerkarten zu verlieren, d. H. Machen Sie den Speichervorgang unsichtbar. Aber was soll man dann sparen? Der Kreis ist geschlossen. Es wurde jedoch eine Lösung gefunden.
Ruhezustand bei Yandex Browser
Viele Leser von Habr konnten bereits erraten, was der Speicher löschen soll, aber das Speichern des Status der Registerkarte ist durchaus möglich, wenn Sie den Status zuerst auf der Festplatte entladen. Wenn Sie auf eine Registerkarte klicken, um die Registerkarte von der Festplatte wiederherzustellen, bemerkt der Benutzer nichts.
Unser Team ist an der Entwicklung des Chromium-Projekts beteiligt, bei dem wichtige
Optimierungsänderungen und neue
Funktionen gesendet werden . Bereits im Jahr 2015
diskutierten wir mit Kollegen aus dem Projekt über die Idee, den Status der Registerkarten auf der Festplatte beizubehalten, und konnten sogar eine Reihe von Verbesserungen vornehmen. Sie beschlossen jedoch, diese Richtung in Chromium einzufrieren. Wir haben uns anders entschieden und uns in Yandex.Browser weiterentwickelt. Es dauerte länger als geplant, aber es hat sich gelohnt. Im Folgenden werden wir über die technische Füllung der Hibernate-Technologie sprechen, aber beginnen wir zunächst mit der allgemeinen Logik.
Mehrmals pro Minute überprüft Yandex.Browser die Menge des verfügbaren Speichers. Wenn dieser unter dem Schwellenwert von 600 Megabyte liegt, kommt der Ruhezustand ins Spiel. Alles beginnt damit, dass der Browser die älteste (zur Verwendung) Hintergrundregisterkarte findet. Übrigens hat der durchschnittliche Benutzer 7 geöffnete Registerkarten, aber 5% haben mehr als 30.
Sie können keine alten Registerkarten aus dem Speicher entladen - Sie können etwas wirklich Wichtiges beschädigen. Zum Beispiel Musik abspielen oder in einem Web Messenger chatten. Derzeit gibt es 28 solcher Ausnahmen. Wenn die Registerkarte nicht in mindestens eine davon passt, überprüft der Browser die nächste.
Wenn eine Registerkarte gefunden wird, die den Anforderungen entspricht, beginnt der Speichervorgang.
Speichern und Wiederherstellen von Registerkarten im Ruhezustand
Jede Seite kann in zwei große Teile unterteilt werden, die den Engines V8 (JS) und Blink (HTML / DOM) zugeordnet sind. Betrachten Sie ein kleines Beispiel:
<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>
Wir haben einen DOM-Baum und ein kleines Skript, das dem Körper einfach ein Div hinzufügt. Aus der Sicht von Blink sieht diese Seite ungefähr so aus:

Schauen wir uns die Beziehung zwischen Blink und V8 am Beispiel von HTMLBodyElement an:

Möglicherweise stellen Sie fest, dass Blink und V8 unterschiedliche Darstellungen derselben Entitäten haben und eng miteinander verbunden sind. Wir kamen also zu der ursprünglichen Idee, den vollständigen Status von V8 zu speichern und Blink nur HTML-Attribute in Form von Text zu speichern. Dies war jedoch ein Fehler, da wir die Zustände von DOM-Objekten verloren haben, die nicht in Attributen gespeichert waren. Wir haben auch Zustände verloren, die nicht im DOM gespeichert waren. Die Lösung für dieses Problem bestand darin, Blink vollständig zu speichern. Aber nicht so einfach.
Zuerst müssen Sie Informationen über Blink-Objekte sammeln. Daher stoppen wir zum Zeitpunkt des Speicherns von V8 nicht nur JS und wandeln es um, sondern sammeln auch Verweise auf DOM-Objekte und andere Hilfsobjekte, die für JS im Speicher verfügbar sind. Wir gehen auch alle Objekte durch, die über die Dokumentobjekte erreichbar sind - die Stammelemente jedes Seitenrahmens. So sammeln wir Informationen über alles, was wichtig ist, um es zu bewahren. Das Schwierigste ist, das Sparen zu lernen.
Wenn wir alle Blink-Klassen zählen, die den DOM-Baum darstellen, sowie verschiedene HTML5-APIs (z. B. Canvas, Medien, Geolocation), erhalten wir Tausende von Klassen. Es ist fast unmöglich, die Logik des Speicherns aller Klassen mit Ihren Händen zu schreiben. Das Schlimmste ist jedoch, dass selbst wenn Sie dies tun, die Wartung unmöglich ist, da wir regelmäßig neue Versionen von Chromium aktualisieren, die unerwartete Änderungen an einer Klasse vornehmen.
Unser Browser für alle Plattformen wurde mit clang erstellt. Um das Problem der Beibehaltung von Blink-Klassen zu lösen, haben wir ein Plugin für clang erstellt, das einen AST (Abstract Syntax Tree) für Klassen erstellt. Zum Beispiel dieser Code:
Klassencode class Bar : public foo_namespace::Foo { struct BarInternal { int int_field_; float float_field_; } bar_internal_field_; std::string string_field_; };
Es wird zu einem solchen XML:
Das Ergebnis des Plugins in 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>
Darüber hinaus generieren andere von uns geschriebene Skripte aus diesen Informationen C ++ - Code zum Speichern und Wiederherstellen von Klassen, der in die Yandex.Browser-Assembly fällt.
C ++ - Sicherungscode, der per Skript aus XML abgerufen wird 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_); }
Insgesamt generieren wir Code für ca. 1000 Blink-Klassen. Zum Beispiel haben wir gelernt, eine so komplexe Klasse wie Canvas zu speichern. Sie können daraus in JS-Code zeichnen, viele Eigenschaften festlegen, Pinselparameter für das Zeichnen festlegen usw. Wir speichern alle diese Eigenschaften, Parameter und das Bild selbst.
Nach dem erfolgreichen Verschlüsseln und Speichern aller Daten auf der Festplatte wird der Registerkartenprozess aus dem Speicher entladen, bis der Benutzer zu dieser Registerkarte zurückkehrt. In der Schnittstelle fällt es nach wie vor nicht auf.
Die Wiederherstellung von Registerkarten erfolgt nicht sofort, jedoch erheblich schneller als beim Herunterladen aus dem Netzwerk. Trotzdem haben wir einen kniffligen Schritt unternommen, um die Benutzer nicht durch Blitze eines weißen Bildschirms zu ärgern. Wir zeigen einen Screenshot der Seite, die während der Speicherphase erstellt wurde. Dies hilft, den Übergang zu glätten. Andernfalls ähnelt der Wiederherstellungsprozess der normalen Navigation, mit dem einzigen Unterschied, dass der Browser keine Netzwerkanforderung stellt. Es erstellt die Rahmenstruktur und die darin enthaltenen DOM-Bäume neu und ersetzt dann den Status von V8.
Wir haben ein Video mit einer klaren Demonstration aufgenommen, wie der Ruhezustand Klick-Registerkarten entlädt und wiederherstellt, während der Fortschritt im JS-Spiel beibehalten wird, der an der Text- und Videoposition eingegeben wurde:
Zusammenfassung
In naher Zukunft wird die Hibernate-Technologie allen Benutzern von Yandex.Browser für Windows zur Verfügung stehen. Wir planen auch, damit in der Alpha-Version für Android zu experimentieren. Damit spart der Browser effizienter als bisher Speicher. Für Benutzer mit einer großen Anzahl geöffneter Registerkarten spart Hibernate beispielsweise durchschnittlich mehr als 330 Megabyte Speicher und verliert keine Informationen auf den Registerkarten, auf die unter allen Netzwerkbedingungen mit einem Klick zugegriffen werden kann. Wir verstehen, dass es für Webmaster nützlich wäre, das Entladen von Hintergrundregistern in Betracht zu ziehen. Daher planen wir, die
Page Lifecycle-API zu unterstützen .
Der Ruhezustand ist nicht unsere einzige Lösung, um Ressourcen zu sparen. Dies ist nicht das erste Jahr, in dem wir daran arbeiten, dass sich der Browser an die im System verfügbaren Ressourcen anpasst. Bei schwachen Geräten wechselt der Browser beispielsweise in den vereinfachten Modus. Wenn der Laptop von der Stromquelle getrennt wird, verringert sich der Stromverbrauch. Ressourcen sparen ist eine große und komplizierte Geschichte, zu der wir auf jeden Fall nach Habré zurückkehren werden.