Cuando los navegadores se quedan sin memoria, descargan las pestañas más antiguas. Esto es molesto porque al hacer clic en dicha pestaña se fuerza la recarga de la página. Hoy les diremos a los lectores de Habr cómo el equipo Yandex.Browser resuelve este problema utilizando la tecnología Hibernate.
Los navegadores basados en cromo crean un proceso para cada pestaña. Este enfoque tiene muchas ventajas. Esto es seguridad (aislamiento de sitios entre sí), estabilidad (el bloqueo de un proceso no arrastra todo el navegador) y la aceleración del trabajo en procesadores modernos con una gran cantidad de núcleos. Pero también hay un inconveniente: un consumo de memoria mayor que cuando se usa un proceso para todo. Si los navegadores no hicieran nada con esto, sus usuarios verían constantemente algo como esto:

En el proyecto Chromium, están luchando con el consumo de memoria de las pestañas de fondo al borrar varias cachés. No se trata del caché en el que se almacenan las imágenes de las páginas cargadas. No hay ningún problema con él: vive en el disco duro. En un navegador moderno, hay muchas otras informaciones almacenadas en la memoria caché.
Además, Chromium ha estado trabajando durante bastante tiempo para detener los temporizadores JS en pestañas de fondo. De lo contrario, limpiar los cachés no tiene sentido, porque las actividades en las pestañas de fondo los restauran. Se cree que si los sitios quieren trabajar en segundo plano, entonces debe usar un trabajador de servicio, no temporizadores.
Si estas medidas no ayudan, solo queda una cosa: descargar todo el proceso de representación de pestañas de la memoria. Un sitio abierto simplemente deja de existir. Si cambia a la pestaña, comienza a descargarse de la red. Si hubo un video de pausa en la pestaña, comenzará a reproducirse desde el principio. Si el formulario se completó en la página, la información ingresada puede perderse. Si una aplicación JS pesada funcionó en la pestaña, deberá iniciarla nuevamente.
El problema de descargar pestañas es especialmente desagradable cuando no hay acceso a la red. ¿Ha pospuesto una pestaña con Habr para leer a bordo de un avión? Esté preparado para que un artículo útil se convierta en una calabaza.
Los desarrolladores de navegadores entienden que esta medida extrema es molesta para los usuarios (simplemente
recurra a la búsqueda para estimar la extensión), por lo que la aplican en el último momento. En este punto, la computadora ya se está desacelerando debido a la falta de memoria, los usuarios lo notan y están buscando formas alternativas de resolver el problema, por lo tanto, por ejemplo, la extensión
The Great Suspender tiene más de 1.4 millones de usuarios.
La gente quiere que se guarden los navegadores y la memoria, y no comienzan a disminuir la velocidad. Para hacer esto, las pestañas no deben descargarse en el último momento, sino un poco antes. Y para esto debes dejar de perder el contenido de las pestañas, es decir Hacer invisible el proceso de guardado. Pero entonces, ¿qué ahorrar? El circulo esta cerrado. Pero se encontró una solución.
Hibernate en el navegador Yandex
Muchos lectores de Habr ya podrían adivinar qué borrar la memoria, pero para guardar el estado de la pestaña es bastante posible si primero descarga el estado en el disco duro. Si hace clic en una pestaña para restaurarla desde el disco duro, el usuario no notará nada.
Nuestro equipo está involucrado en el desarrollo del proyecto Chromium, donde envía
ediciones de optimización significativas y nuevas
características . En 2015,
discutimos con colegas del proyecto la idea de mantener el estado de las pestañas en el disco duro e incluso logramos hacer una serie de mejoras, pero decidieron congelar esta dirección en Chromium. Decidimos un desarrollo diferente y continuo en Yandex.Browser. Tomó más tiempo de lo planeado, pero valió la pena. A continuación hablaremos sobre el relleno técnico de la tecnología Hibernate, pero por ahora, comencemos con la lógica general.
Varias veces por minuto, Yandex.Browser comprueba la cantidad de memoria disponible y, si es inferior al valor umbral de 600 megabytes, entra en juego Hibernate. Todo comienza con el hecho de que el navegador encuentra la pestaña de fondo más antigua (para uso). Por cierto, el usuario promedio tiene 7 pestañas abiertas, pero el 5% tiene más de 30.
No puede descargar ninguna pestaña anterior de la memoria, puede romper algo realmente importante. Por ejemplo, reproducir música o chatear en un mensajero web. Existen 28 excepciones de este tipo ahora. Si la pestaña no encaja en al menos una de ellas, el navegador procede a verificar la siguiente.
Si se encuentra una pestaña que cumple con los requisitos, entonces comienza el proceso de guardarla.
Guardar y restaurar pestañas en Hibernate
Cualquier página se puede dividir en dos partes grandes, asociadas con los motores V8 (JS) y Blink (HTML / DOM). Considere un pequeño ejemplo:
<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>
Tenemos un árbol DOM y un pequeño script que simplemente agrega un div al cuerpo. Desde el punto de vista de Blink, esta página se ve así:

Veamos la relación entre Blink y V8 usando el ejemplo HTMLBodyElement:

Puede notar que Blink y V8 tienen diferentes representaciones de las mismas entidades y están estrechamente relacionados entre sí. Así que llegamos a la idea original: guardar el estado completo de V8 y que Blink almacene solo atributos HTML en forma de texto. Pero esto fue un error, porque perdimos esos estados de objetos DOM que no estaban almacenados en atributos. También perdimos estados que no estaban almacenados en el DOM. La solución a este problema fue salvar Blink por completo. Pero no tan simple.
Primero debe recopilar información sobre los objetos Blink. Por lo tanto, en el momento de guardar V8, no solo detenemos JS y lo convertimos, sino que también recopilamos referencias a objetos DOM y otros objetos auxiliares disponibles para JS en la memoria. También revisamos todos los objetos a los que se puede llegar desde los objetos del documento: los elementos raíz de cada marco de página. Por lo tanto, recopilamos información sobre todo lo que es importante preservar. La parte más difícil es aprender a ahorrar.
Si contamos todas las clases de Blink que representan el árbol DOM, así como diferentes API HTML5 (por ejemplo, lienzo, medios, geolocalización), obtenemos miles de clases. Es casi imposible escribir la lógica de guardar todas las clases con las manos. Pero la peor parte es que, incluso si hace esto, será imposible mantenerlo, porque actualizamos regularmente nuevas versiones de Chromium que realizan cambios inesperados en cualquier clase.
Nuestro navegador para todas las plataformas está construido usando clang. Para resolver el problema de preservar las clases de Blink, creamos un complemento para clang, que crea un AST (árbol de sintaxis abstracta) para las clases. Por ejemplo, este código:
Código de clase class Bar : public foo_namespace::Foo { struct BarInternal { int int_field_; float float_field_; } bar_internal_field_; std::string string_field_; };
Se convierte en tal XML:
El resultado del complemento 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>
Además, otros scripts escritos por nosotros generan código C ++ a partir de esta información para guardar y restaurar clases, que se incluyen en el ensamblado Yandex.Browser.
C ++ guardar código obtenido por script desde 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_); }
En total, generamos código para aproximadamente 1000 clases de Blink. Por ejemplo, aprendimos a guardar una clase tan compleja como Canvas. Puede dibujar en código JS, establecer muchas propiedades, establecer parámetros de pincel para dibujar, etc. Guardamos todas estas propiedades, parámetros y la imagen en sí.
Después de cifrar y guardar con éxito todos los datos en el disco duro, el proceso de pestañas se descarga de la memoria hasta que el usuario regrese a esta pestaña. En la interfaz, como antes, no se destaca.
La recuperación de pestañas no es instantánea, pero es significativamente más rápida que cuando se descarga desde la red. Sin embargo, hicimos un movimiento complicado para no molestar a los usuarios con flashes de una pantalla en blanco. Mostramos una captura de pantalla de la página creada durante la fase de guardado. Esto ayuda a suavizar la transición. De lo contrario, el proceso de recuperación es similar a la navegación normal, con la única diferencia de que el navegador no realiza una solicitud de red. Recrea la estructura del marco y los árboles DOM en ellos, y luego reemplaza el estado de V8.
Grabamos un video con una demostración clara de cómo Hibernate descarga y restaura las pestañas de clic mientras preserva el progreso en el juego JS ingresado en la posición de texto y video:
Resumen
En un futuro próximo, la tecnología Hibernate estará disponible para todos los usuarios de Yandex.Browser para Windows. También planeamos comenzar a experimentar con él en la versión alfa para Android. Con él, el navegador ahorra memoria de manera más eficiente que antes. Por ejemplo, para los usuarios con una gran cantidad de pestañas abiertas, Hibernate ahorra en promedio más de 330 megabytes de memoria y no pierde información en las pestañas, que permanece accesible con un solo clic en cualquier condición de red. Entendemos que sería útil para los webmasters considerar la descarga de pestañas de fondo, por lo que planeamos admitir la
API del ciclo de vida de la
página .
Hibernate no es nuestra única solución destinada a ahorrar recursos. Este no es el primer año que hemos estado trabajando para garantizar que el navegador se adapte a los recursos disponibles en el sistema. Por ejemplo, en dispositivos débiles, el navegador ingresa al modo simplificado, y cuando la computadora portátil se desconecta de la fuente de alimentación, reduce el consumo de energía. Ahorrar recursos es una historia grande y complicada, a la que definitivamente volveremos a Habré.