Quando os navegadores ficam sem memória, eles descarregam as guias mais antigas. Isso é irritante porque clicar nessa guia força o recarregamento da página. Hoje, contaremos aos leitores da Habr como a equipe do Yandex.Browser resolve esse problema usando a tecnologia Hibernate.
Navegadores baseados em Chromium criam um processo para cada guia. Essa abordagem tem muitas vantagens. Isso é segurança (isolamento de sites um do outro), estabilidade (falha de um processo não arrasta o navegador inteiro) e aceleração do trabalho em processadores modernos com um grande número de núcleos. Mas também há menos - um consumo de memória maior do que quando se usa um processo para tudo. Se os navegadores não fizessem nada com isso, seus usuários sempre veriam algo assim:

No projeto Chromium, eles estão lutando com o consumo de memória das guias em segundo plano limpando vários caches. Não se trata do cache no qual as imagens das páginas carregadas são armazenadas. Não há nenhum problema com ele - ele mora no disco rígido. Em um navegador moderno, existem muitas outras informações em cache armazenadas na RAM.
Além disso, o Chromium trabalha há algum tempo para parar os cronômetros JS nas guias em segundo plano. Caso contrário, limpar os caches não faz sentido, porque atividades nas guias em segundo plano as restauram. Acredita-se que, se os sites quiserem trabalhar em segundo plano, será necessário usar um trabalhador de serviço, não temporizadores.
Se essas medidas não ajudarem, resta apenas uma coisa: descarregar todo o processo de renderização de guias da memória. Um site aberto simplesmente deixa de existir. Se você alternar para a guia, ele inicia o download da rede. Se houver um vídeo de pausa na guia, ele começará a ser reproduzido desde o início. Se o formulário foi preenchido na página, as informações inseridas podem ser perdidas. Se um aplicativo JS pesado funcionou na guia, será necessário iniciá-lo novamente.
O problema de descarregar guias é especialmente desagradável quando não há acesso à rede. Você adiou uma guia com Habr para ler a bordo de um avião? Esteja preparado para que um artigo útil se transforme em abóbora.
Os desenvolvedores de navegadores entendem que essa medida extrema é irritante para os usuários (basta
recorrer à pesquisa para estimar a extensão), para aplicá-la no último momento. No momento, o computador já está ficando lento devido à falta de memória, os usuários percebem isso e estão procurando maneiras alternativas de resolver o problema; portanto, por exemplo,
a extensão
The Great Suspender tem mais de 1,4 milhão de usuários.
As pessoas querem que os navegadores e a memória sejam salvos e não começam a ficar mais lentos. Para fazer isso, as guias não devem ser descarregadas no último momento, mas um pouco antes. E para isso, você precisa parar de perder o conteúdo das guias, ou seja, torne o processo de economia invisível. Mas então o que economizar? O círculo está fechado. Mas uma solução foi encontrada.
Hibernate no navegador Yandex
Muitos leitores Habr já podem adivinhar o que limpar a memória, mas salvar o estado da guia é bem possível se você primeiro descarregar o estado no disco rígido. Se você clicar em uma guia para restaurar a guia a partir do disco rígido, o usuário não notará nada.
Nossa equipe está envolvida no desenvolvimento do projeto Chromium, onde ele envia
edições otimizadas significativas e novos
recursos . Em 2015,
discutimos com os colegas do projeto a idéia de manter o estado das guias no disco rígido e até conseguimos fazer várias melhorias, mas eles decidiram congelar essa área no Chromium. Decidimos de forma diferente e continuamos o desenvolvimento no Yandex.Browser. Demorou mais tempo do que o planejado, mas valeu a pena. A seguir, falaremos sobre o detalhamento técnico da tecnologia Hibernate, mas, por enquanto, vamos começar com a lógica geral.
Várias vezes por minuto, o Yandex.Browser verifica a quantidade de memória disponível e, se for menor que o valor limite de 600 megabytes, o Hibernate entra em jogo. Tudo começa com o fato de o navegador encontrar a guia de plano de fundo mais antiga (para uso). A propósito, o usuário médio tem 7 guias abertas, mas 5% tem mais de 30.
Você não pode descarregar nenhuma guia antiga da memória - pode quebrar algo realmente importante. Por exemplo, tocando música ou conversando em um web messenger. Agora existem 28 exceções: se a guia não couber em pelo menos uma delas, o Navegador continua a verificação da próxima.
Se for encontrada uma guia que atenda aos requisitos, o processo de salvá-la será iniciado.
Salvando e restaurando guias no Hibernate
Qualquer página pode ser dividida em duas partes grandes, associadas aos mecanismos V8 (JS) e Blink (HTML / DOM). Considere um pequeno exemplo:
<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>
Temos uma árvore DOM e um pequeno script que simplesmente adiciona uma div ao corpo. Do ponto de vista do Blink, esta página é mais ou menos assim:

Vejamos o relacionamento entre Blink e V8 usando o exemplo HTMLBodyElement:

Você pode perceber que Blink e V8 têm representações diferentes das mesmas entidades e estão intimamente relacionados entre si. Então, chegamos à ideia original - salvar o estado completo da V8 e o Blink armazenar apenas atributos HTML na forma de texto. Mas isso foi um erro, porque perdemos os estados de objetos DOM que não foram armazenados em atributos. Também perdemos estados que não foram armazenados no DOM. A solução para esse problema foi salvar o Blink completamente. Mas não é tão simples.
Primeiro, você precisa coletar informações sobre os objetos Blink. Portanto, no momento de salvar a V8, não apenas paramos o JS e o convertemos, mas também coletamos referências a objetos DOM e outros objetos auxiliares disponíveis para JS na memória. Também examinamos todos os objetos que podem ser alcançados a partir dos objetos do documento - os elementos raiz de cada quadro de página. Portanto, coletamos informações sobre tudo o que é importante preservar. A parte mais difícil é aprender a economizar.
Se contarmos todas as classes Blink que representam a árvore DOM, bem como diferentes APIs HTML5 (por exemplo, tela, mídia, localização geográfica), obteremos milhares de classes. É quase impossível escrever a lógica de salvar todas as classes com as mãos. Mas a pior parte é que, mesmo se você fizer isso, será impossível mantê-lo, porque atualizamos regularmente novas versões do Chromium que fazem alterações inesperadas em qualquer classe.
Nosso navegador para todas as plataformas é construído usando clang. Para resolver o problema de preservar as classes Blink, criamos um plug-in para clang, que cria uma AST (árvore de sintaxe abstrata) para classes. Por exemplo, este código:
Código da classe class Bar : public foo_namespace::Foo { struct BarInternal { int int_field_; float float_field_; } bar_internal_field_; std::string string_field_; };
Transforma-se em tal XML:
O resultado do plug-in em 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>
Além disso, outros scripts escritos por nós geram código C ++ a partir dessas informações para salvar e restaurar classes, que se enquadram no assembly Yandex.Browser.
C ++ salva código obtido por script 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_); }
No total, geramos código para cerca de 1000 classes Blink. Por exemplo, aprendemos a salvar uma classe tão complexa como o Canvas. Você pode desenhar a partir dele no código JS, definir muitas propriedades, definir parâmetros de pincel para desenhar e assim por diante. Nós salvamos todas essas propriedades, parâmetros e a própria imagem.
Após criptografar e salvar com êxito todos os dados no disco rígido, o processo da guia é descarregado da memória até o usuário retornar a essa guia. Na interface, como antes, ela não se destaca.
A recuperação de guias não é instantânea, mas significativamente mais rápida do que quando o download é feito na rede. No entanto, fizemos uma jogada complicada para não incomodar os usuários com flashes de uma tela branca. Mostramos uma captura de tela da página criada durante a fase de salvamento. Isso ajuda a suavizar a transição. Caso contrário, o processo de recuperação é semelhante à navegação normal, com a única diferença: o navegador não faz uma solicitação de rede. Ele recria a estrutura do quadro e as árvores DOM nelas e substitui o estado da V8.
Gravamos um vídeo com uma demonstração clara de como o Hibernate descarrega e restaura guias de cliques, preservando o progresso no jogo JS inserido na posição de texto e vídeo:
Sumário
Em um futuro próximo, a tecnologia Hibernate estará disponível para todos os usuários do Yandex.Browser for Windows. Também planejamos começar a experimentar na versão alfa para Android. Com isso, o Navegador economiza memória com mais eficiência do que antes. Por exemplo, para usuários com um grande número de guias abertas, o Hibernate economiza em média mais de 330 megabytes de memória e não perde informações nas guias, que permanecem acessíveis em um clique em qualquer condição de rede. Entendemos que seria útil para os webmasters considerar o descarregamento de guias em segundo plano, por isso planejamos oferecer suporte à
API Page Lifecycle API .
O Hibernate não é nossa única solução destinada a economizar recursos. Este não é o primeiro ano em que trabalhamos para garantir que o Navegador se adapte aos recursos disponíveis no sistema. Por exemplo, em dispositivos fracos, o Navegador entra no modo simplificado e, quando o laptop é desconectado da fonte de energia, reduz o consumo de energia. A economia de recursos é uma história grande e complicada, à qual retornaremos definitivamente a Habré.