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:
- Criado com freqüência: um
Bullet
objeto em um jogo de balas .
- Estrutura profunda: Árvore de decisão para uma implementação de IA.
Você deve usar a Stack
como seu pool (diferente da maioria das implementações que usam a Queue
). 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 fazer Clear()
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 a LinkedList
.
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 uma O(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:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)
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:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection
O padrão Disposer ajuda a configurar uma maneira previsível de solicitar aos objetos que limpem seus campos particulares. Por exemplo:
public class MyClass : IDisposable
{
private MyNestedType _nested;
// A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
// ~MyClass() { }
public void Dispose()
{
_nested = null;
}
}