Quebrando as Regras da Coleta de Lixo da Unidade

Era uma vez, um programador de jogos de unidade chamado Lancelot. Muito apaixonado, eu diria. Ele ainda não sabia, mas acabaria enfrentando o lado mais sombrio da coleta de lixo da Unity.

imagem

Lancelot estava sempre procurando títulos cada vez maiores para trabalhar. E assim ele trabalhou duro para obter sua grande chance na indústria de jogos.

Não era fácil, ele sabia.

Esses pontos na indústria de jogos eram e ainda são reservados para uma pequena minoria de programadores de jogos. E ele não tinha certeza se estaria de acordo com os padrões.

Mas ele persistiu e continuou a aprimorar suas habilidades de programação.

Lancelot começou a trabalhar em pequenos jogos. Talvez em algum momento ele tivesse a grande chance que procurava, pensou.

Anos se passaram até que ele teve a oportunidade que estava esperando. Ele foi convidado a portar um grande jogo de VR para uma plataforma móvel. Tão animado quanto Lancelot estava, ele não conseguia parar de se perguntar se era bom o suficiente para a tarefa. Foi assustador, mas ele aceitou o desafio. Ele sabia que só poderia crescer com isso.

A maior preocupação de Lancelot era a necessidade de melhorar enormemente o desempenho do jogo. Na verdade, era um duplo desafio. Ele teve que melhorar o desempenho em 20% para obter uma plataforma significativamente menos poderosa .

Após meses de trabalho sem parar, ele finalmente conseguiu otimizar o jogo o suficiente para ter uma linha de base de desempenho sólida.

No entanto, um problema inesperado estava ao virar da esquina ...

O criador de perfil da unidade revelou a ele uma queda significativa na taxa de quadros a cada poucos segundos. E isso o preocupou, porque isso não permitiria que ele enviasse o jogo. O lançamento do jogo estava em risco. Isso o deixou totalmente desconfortável.

Com base em sua experiência anterior, Lancelot rapidamente suspeitou do coletor de lixo . Afinal, ele sabia que alocar memória temporária no jogo com muita frequência poderia causar esses picos de desempenho. Assim como a lata de lixo na cozinha de todos, você sabe que é hora de limpá-la quando atingir 80% de sua capacidade.

E assim ele passou dias lutando contra as alocações incômodas de memória. Ele realizou todos os tipos de otimizações em que conseguia pensar. Conjuntos de objetos, cache de dados, otimizações da estrutura de dados ...

Passar esses dias otimizando o levou um pouco à frente na jornada de desempenho. Lancelot estava orgulhoso de seu trabalho, mas suas preocupações só aumentaram quando ele viu o coletor de lixo ainda em funcionamento a cada 15 segundos.

Jogar o jogo em VR com essas quedas de desempenho de alguma forma faria as pessoas parecerem pálidas.

"Como isso pode estar acontecendo?", Ele se perguntou.

Com mais paciência e busca, Lancelot descobriu uma segunda fonte de alocações de memória que ele não via antes. Isso aconteceu em uma biblioteca de terceiros .

Ele deu uma olhada e logo percebeu que estava na pior posição de todos os tempos: aquela biblioteca era de código fechado. Além disso, ele também tentou usar o coletor de lixo incremental do Unity, mas não teve condições de pagar seu preço de desempenho.

Lancelot estava ficando sem opções.

Ele se sentiu desesperado, mas conseguiu manter a calma. Ele esteve em situações piores, afinal.

Ele poderia fazer engenharia reversa da biblioteca e fazer otimizações de alocação de memória. O problema era que a licença não permitia essas coisas. E ele era jovem demais para ir para a cadeia.

A segunda opção que ele considerou foi pré-alocar muita memória no heap. Ele sabia que a unidade desencadeou o processo de coleta de lixo quando o uso da pilha atingiu uma certa porcentagem. Portanto, aumentar a pilha deve dar a ele mais tempo entre as coletas de lixo.

Infelizmente, isso ainda não foi suficiente.

Lentamente, parecia que ele não tinha controle sobre a situação. Foi difícil, mas novamente, ele persistiu .

Então Lancelot teve uma ideia maluca . E se ele desativasse completamente a coleta de lixo? Isso foi possível? Ele sentia em seus ossos o quão arriscada era essa ideia. Ele não queria acrescentar a possibilidade de o jogo travar em pontos imprevisíveis. A última vez que ele checou, ​​isso não foi divertido para os jogadores. Talvez os tempos tenham mudado, mas é melhor prevenir do que remediar.

Além disso, ele se preocupava em adiar o lançamento do jogo. Ele não queria que seus jogadores perdessem esse título no Natal. Ele se lembrou de como se divertia jogando EverQuest durante essas férias. Ele não tiraria isso dos jogadores.

Chegou a esse ponto, ele não tinha outra escolha senão desativar o coletor de lixo.

Ele entrou no modo de pesquisa e descobriu que realmente podia desativar manualmente a coleta de lixo . Ele realizou dezenas de experimentos para ver quanto tempo o jogo duraria sem ficar sem memória. Ele fez todos os tipos de testes para estressar o sistema. Clicando em todos os lugares, andando e pulando, alternando entre diferentes aplicativos.

Os números começaram a chegar em sua planilha: 25 minutos, 28 minutos, 30 minutos ... Ele também observou como o uso da pilha aumentava ao longo do tempo para garantir que ele nunca excederia um orçamento seguro.

Com esses números, Lancelot estabeleceu uma margem de segurança generosa e preparou um protótipo . Ele executava a coleta de lixo manualmente durante o carregamento das telas e a cada poucos minutos.

Ele tinha esperança novamente.

Ele pediu educadamente ao controle de qualidade que passasse pelo jogo dezenas de vezes.

A memória estava sempre dentro do orçamento. Sem falhas. Sem efeitos colaterais.

Essa longa jornada o levou ao ponto em que ele foi capaz de embarcar o jogo.

E adivinhe? Agora, centenas de jogadores estão gostando do Natal.

No começo, ele não estava confortável com esta solução. Foi uma jogada arriscada e ele sabia disso. Mas ele conseguiu.

Lancelot aprendeu a se sentir confortável com o desconfortável . Ele aprendeu a ser mais pragmático . Porque há momentos em que um programador precisa ser.

Alguma coisa da história toca uma campainha? Nesse caso, sua intuição provavelmente está certa.

Esse programador era eu.

Nos momentos em que você precisar, é assim que você pode gerenciar o coletor de lixo:

Esse trecho de código mostra como desativar as coletas de lixo automáticas. Ele executa o processo do GC manualmente a cada minuto e, possivelmente, durante as transições da tela (desbotamento para preto).

Esteja ciente de seus possíveis efeitos colaterais:

  • Falhas : se você não jogar com segurança o suficiente, ficará sem memória. Pior, o sistema operacional pode acabar com o seu jogo quando você alterna entre aplicativos
  • Tempos de coleta de lixo mais longos : aumentar a pilha tornará as futuras coleções de lixo mais lentas

Se você precisar produzir quantidades generosas de lixo, eis um método simples que funcionará:

public class GenerousGarbageCreator : MonoBehaviour { [SerializeField] private int garbageCreationRate = 1024; private static int[] _garbage; void Update() { _garbage = new int[garbageCreationRate]; } } 

Isto é o que você obterá no criador de perfil:

imagem
Coleta de lixo da Unity: acionador manual baseado em tempo

Lá você vê um uso crescente de memória. O crescente uso de heap é destacado como "mono". Felizmente para nós, estamos executando o coletor de lixo manual a cada 3 segundos.

Você pode ver claramente esse ciclo de remoção de geração de lixo no gráfico do criador de perfil. Para os desenvolvedores de jogos que estudaram física, você pode reconhecê-la como uma forma de onda dente de serra.

Se você deseja o código fonte deste projeto, sabe onde encontrá-lo (spoiler: aqui).

Para otimizações de memória mais gerais, você pode estar interessado em Endereçáveis ​​da Unidade. Com os endereços endereçáveis, você reduz o uso total de memória para ativar a coleta de lixo com menos frequência. Por sua vez, isso reduzirá os picos de desempenho que seus jogadores experimentarão.

Estou ansioso para trabalhar com todos vocês em 2020.
Ruben

Source: https://habr.com/ru/post/pt484710/


All Articles