* Até onde eu sei, o Unity3D para iOS é baseado no tempo de execução do Mono e o Mono possui apenas GC geracional de marcação e varredura.
Este sistema de GC não pode evitar o tempo de GC, o que interrompe o sistema de jogo. O pool de instâncias pode reduzir isso, mas não completamente, porque não podemos controlar a instanciação na biblioteca de classes base do CLR. Essas instâncias pequenas e frequentes ocultas aumentarão o tempo não-determinístico do GC. Forçar o GC completo periodicamente prejudicará muito o desempenho (o Mono pode forçar o GC completo, na verdade?)
Então, como posso evitar esse tempo de GC ao usar o Unity3D sem degradar o desempenho?
unity
performance
Eonil
fonte
fonte
Respostas:
Felizmente, como você apontou, as compilações COMPACT Mono usam um GC geracional (em forte contraste com as da Microsoft, como o WinMo / WinPhone / XBox, que apenas mantém uma lista simples).
Se o seu jogo é simples, o GC deve lidar com isso muito bem, mas aqui estão algumas dicas que você pode querer examinar.
Otimização prematura
Primeiro, verifique se isso é realmente um problema para você antes de tentar corrigi-lo.
Agrupando tipos de referência caros
Você deve agrupar os tipos de referência que você cria com frequência ou que possuem estruturas profundas. Um exemplo de cada um seria:
Bullet
objeto em um jogo de balas .Você deve usar a
Stack
como seu pool (diferente da maioria das implementações que usam aQueue
). A razão para isso é porque, com a,Stack
se você retorna um objeto para a piscina e outra coisa o agarra imediatamente; ele terá uma chance muito maior de estar em uma página ativa - ou mesmo no cache da CPU, se você tiver sorte. É um pouco mais rápido. Além disso, limite sempre o tamanho das suas piscinas (apenas desconsidere 'check-ins' se o seu limite tiver sido excedido).Evite criar novas listas para eliminá-las
Não crie um novo
List
quando você realmente quis fazerClear()
isso. Você pode reutilizar a matriz de back-end e salvar uma carga de alocações e cópias de matriz. Da mesma forma que isso, tente criar listas com uma capacidade inicial significativa (lembre-se, isso não é um limite - apenas uma capacidade inicial) - não precisa ser preciso, apenas uma estimativa. Isso deve se aplicar basicamente a qualquer tipo de coleção - exceto aLinkedList
.Use matrizes de estruturas (ou listas) sempre que possível
Você obtém pouco benefício com o uso de estruturas (ou tipos de valor em geral) se as distribuir entre objetos. Por exemplo, na maioria dos 'bons' sistemas de partículas, as partículas individuais são armazenadas em uma matriz massiva: a matriz e o índice são passados ao invés da própria partícula. A razão pela qual isso funciona tão bem é porque, quando o GC precisa coletar a matriz, pode pular completamente o conteúdo (é uma matriz primitiva - nada a fazer aqui). Então, em vez de olhar para 10.000 objetos, o GC simplesmente precisa olhar para 1 matriz: enorme ganho! Novamente, isso funcionará apenas com tipos de valor .
Depois do RoyT. forneceu algum feedback viável e construtivo, sinto que preciso expandir mais isso. Você só deve usar essa técnica quando estiver lidando com grandes quantidades de entidades (milhares a dezenas de milhares). Além disso, deve ser uma estrutura que não deve ter nenhum campo de tipo de referência e deve estar em uma matriz de tipo explícito. Ao contrário do feedback dele, estamos colocando-o em uma matriz que provavelmente é um campo de uma classe - o que significa que ele vai acabar com o heap (não estamos tentando evitar uma alocação de heap - apenas evitando o trabalho do GC). Nós realmente nos preocupamos com o fato de que é um pedaço de memória contíguo com muitos valores que o GC pode simplesmente olhar em uma
O(1)
operação em vez de umaO(n)
operação.Você também deve alocar essas matrizes o mais próximo possível da inicialização do aplicativo para reduzir as chances de ocorrência de fragmentação ou de trabalho excessivo, pois o GC tenta mover esses pedaços, e considere usar uma lista vinculada híbrida em vez do
List
tipo interno )GC.Collect ()
Este é definitivamente o melhor maneira de dar um tiro no pé (veja: "Considerações sobre desempenho") com um GC de gerações. Você deve chamá-lo apenas quando tiver criado uma quantidade EXTREMA de lixo - e a única instância em que isso pode ser um problema é logo após o carregamento do conteúdo de um nível - e mesmo assim você provavelmente deve coletar apenas a primeira geração (
GC.Collect(0);
) esperançosamente impedir a promoção de objetos para a terceira geração.IDisposable e Anulação de Campo
Vale a pena anular campos quando você não precisa mais de um objeto (mais ainda em objetos restritos). O motivo está nos detalhes de como o GC funciona: ele remove apenas objetos que não estão enraizados (ou seja, referenciados), mesmo que esse objeto tenha sido desaprotegido por causa de outros objetos sendo removidos na coleção atual ( nota: isso depende do GC sabor em uso - alguns realmente limpam as correntes). Além disso, se um objeto sobreviver a uma coleção, ele será imediatamente promovido para a próxima geração - isso significa que qualquer objeto deixado nos campos será promovido durante uma coleção. Cada geração sucessiva é exponencialmente mais cara de coletar (e ocorre com pouca frequência).
Veja o seguinte exemplo:
E se
MyFurtherNestedObject
um objeto com vários megabytes, é possível garantir que o GC não o veja por um longo tempo - porque você o promoveu inadvertidamente para o G3. Compare isso com este exemplo:O padrão Disposer ajuda a configurar uma maneira previsível de solicitar aos objetos que limpem seus campos particulares. Por exemplo:
fonte
Pouca instanciação dinâmica acontece automaticamente dentro da biblioteca base, a menos que você chame algo que exija. A grande maioria da alocação e desalocação de memória vem do seu próprio código e você pode controlar isso.
Pelo que entendi, você não pode evitar isso completamente - tudo o que você pode fazer é garantir que você recicle e agrupe objetos sempre que possível. Use estruturas em vez de classes, evite o sistema UnityGUI e geralmente evite criar novos objetos na memória dinâmica durante períodos de tempo. quando o desempenho é importante.
Você também pode forçar a coleta de lixo em determinados horários, o que pode ou não ajudar: consulte algumas das sugestões aqui: http://unity3d.com/support/documentation/Manual/iphone-Optimizing-Scripts.html
fonte
GC.Collect()
= memória livre agora, mas problemas muito prováveis mais tarde.