Eu tenho um aplicativo Javascript bastante complexo, que tem um loop principal que é chamado 60 vezes por segundo. Parece haver muita coleta de lixo acontecendo (com base na saída 'dente de serra' da linha do tempo do Memory nas ferramentas de desenvolvimento do Chrome) - e isso geralmente afeta o desempenho do aplicativo.
Portanto, estou tentando pesquisar as melhores práticas para reduzir a quantidade de trabalho que o coletor de lixo tem que fazer. (A maioria das informações que consegui encontrar na web diz respeito a evitar vazamentos de memória, o que é uma questão um pouco diferente - minha memória está sendo liberada, só que há muita coleta de lixo em andamento.) Estou assumindo que isso se resume principalmente em reutilizar objetos tanto quanto possível, mas é claro que o diabo está nos detalhes.
O aplicativo é estruturado em 'classes' ao longo das linhas de Herança JavaScript simples de John Resig .
Acho que um problema é que algumas funções podem ser chamadas milhares de vezes por segundo (visto que são usadas centenas de vezes durante cada iteração do loop principal) e talvez as variáveis de trabalho locais nessas funções (strings, arrays, etc.) pode ser o problema.
Estou ciente do agrupamento de objetos para objetos maiores / mais pesados (e usamos isso até certo ponto), mas estou procurando técnicas que podem ser aplicadas em todos os setores, especialmente relacionadas a funções que são chamadas muitas vezes em loops estreitos .
Que técnicas posso usar para reduzir a quantidade de trabalho que o coletor de lixo deve fazer?
E, talvez também - que técnicas podem ser empregadas para identificar quais objetos estão sendo mais coletados? (É uma base de código muito grande, portanto, comparar os instantâneos do heap não foi muito proveitoso)
fonte
Respostas:
Muitas coisas que você precisa fazer para minimizar a rotatividade de GC vão contra o que é considerado JS idiomático na maioria dos outros cenários, portanto, tenha em mente o contexto ao julgar o conselho que dou.
A alocação acontece em intérpretes modernos em vários lugares:
new
ou por meio da sintaxe literal[...]
, ou{}
.(function (...) { ... })
.Object(myNumber)
ouNumber.prototype.toString.call(42)
Array.prototype.slice
.arguments
para refletir sobre a lista de parâmetros.Evite fazer isso e agrupe e reutilize objetos sempre que possível.
Especificamente, procure oportunidades para:
split
ou correspondências de expressão regular, pois cada uma requer várias alocações de objetos. Isso freqüentemente acontece com chaves em tabelas de pesquisa e IDs de nós dinâmicos do DOM. Por exemplo,lookupTable['foo-' + x]
edocument.getElementById('foo-' + x)
ambos envolvem uma alocação, pois há uma concatenação de string. Freqüentemente, você pode anexar chaves a objetos de longa duração em vez de reconcatenar. Dependendo dos navegadores que você precisa oferecer suporte, você pode usarMap
para usar objetos como chaves diretamente.try { op(x) } catch (e) { ... }
, façaif (!opCouldFailOn(x)) { op(x); } else { ... }
.JSON.stringify
que usa um buffer nativo interno para acumular conteúdo em vez de alocar vários objetos.arguments
funções desde que usam isso para criar um objeto do tipo array quando chamado.Sugeri usar
JSON.stringify
para criar mensagens de rede de saída. Analisar mensagens de entrada usandoJSON.parse
obviamente envolve alocação, e muito disso para mensagens grandes. Se você pode representar suas mensagens recebidas como matrizes de primitivas, então você pode economizar muitas alocações. O único outro componente integrado ao qual você pode construir um analisador que não aloca éString.prototype.charCodeAt
. No entanto, um analisador para um formato complexo que só usa isso vai ser um inferno de ler.fonte
JSON.parse
objetos d alocam menos (ou igual) espaço do que a string da mensagem?As ferramentas de desenvolvedor do Chrome têm um recurso muito bom para rastrear a alocação de memória. É chamado de Linha do Tempo da Memória. Este artigo descreve alguns detalhes. Suponho que é disso que você está falando sobre o "dente de serra"? Este é o comportamento normal para a maioria dos tempos de execução com GC. A alocação continua até que um limite de uso seja atingido, acionando uma coleção. Normalmente, existem diferentes tipos de coleções em diferentes limites.
As coletas de lixo são incluídas na lista de eventos associada ao rastreamento junto com sua duração. No meu notebook bastante antigo, coletas efêmeras estão ocorrendo em cerca de 4 Mb e levam 30 ms. Isso é 2 de suas iterações de loop de 60Hz. Se esta for uma animação, as coleções de 30ms provavelmente estão causando falhas. Você deve começar aqui para ver o que está acontecendo em seu ambiente: onde está o limite de coleta e quanto tempo suas coletas estão levando. Isso fornece um ponto de referência para avaliar as otimizações. Mas você provavelmente não fará melhor do que diminuir a frequência da gagueira diminuindo a taxa de alocação, aumentando o intervalo entre as coleções.
A próxima etapa é usar a opção Perfis | Recurso Record Heap Allocations para gerar um catálogo de alocações por tipo de registro. Isso mostrará rapidamente quais tipos de objeto estão consumindo mais memória durante o período de rastreamento, o que é equivalente à taxa de alocação. Concentre-se neles em ordem decrescente de taxa.
As técnicas não são ciência de foguetes. Evite objetos encaixotados quando você pode fazer com um não encaixotado. Use variáveis globais para manter e reutilizar objetos em uma única caixa em vez de alocar novos em cada iteração. Agrupe tipos de objetos comuns em listas gratuitas em vez de abandoná-los. Resultados da concatenação de string de cache que provavelmente podem ser reutilizados em iterações futuras. Evite a alocação apenas para retornar os resultados da função definindo variáveis em um escopo delimitador. Você terá que considerar cada tipo de objeto em seu próprio contexto para encontrar a melhor estratégia. Se precisar de ajuda com detalhes, poste uma edição descrevendo os detalhes do desafio que você está olhando.
Aconselho a não perverter seu estilo de codificação normal em um aplicativo em uma tentativa de espingarda de produzir menos lixo. É pela mesma razão que você não deve otimizar a velocidade prematuramente. A maior parte do seu esforço mais a maior complexidade e obscuridade do código não fará sentido.
fonte
request animation frame
,animation frame fired
, ecomposite layers
. Não tenho ideia de por que não estou vendoGC Event
como você (esta é a última versão do Chrome, e também canário).@342342
ecode relocation info
.Como princípio geral, você deseja armazenar em cache o máximo possível e criar e destruir o mínimo possível para cada execução do loop.
A primeira coisa que me vem à cabeça é reduzir o uso de funções anônimas (se houver) dentro do seu loop principal. Além disso, seria fácil cair na armadilha de criar e destruir objetos que são passados para outras funções. Não sou um especialista em javascript, mas imagino que este:
seria executado muito mais rápido do que isso:
Existe algum tempo de inatividade para o seu programa? Talvez você precise que ele funcione sem problemas por um ou dois segundos (por exemplo, para uma animação) e então tenha mais tempo para processar? Se for esse o caso, eu poderia ver pegando objetos que normalmente seriam coletados como lixo em toda a animação e mantendo uma referência a eles em algum objeto global. Então, quando a animação terminar, você pode limpar todas as referências e deixar o coletor de lixo fazer o trabalho.
Desculpe se isso tudo é um pouco trivial em comparação com o que você já tentou e pensou.
fonte
Eu faria um ou alguns objetos no
global scope
(onde tenho certeza que o coletor de lixo não tem permissão para tocá-los), então tentaria refatorar minha solução para usar esses objetos para fazer o trabalho, em vez de usar variáveis locais .É claro que isso não poderia ser feito em todo o código, mas geralmente essa é minha maneira de evitar o coletor de lixo.
PS: pode tornar essa parte específica do código um pouco menos sustentável.
fonte