Eu estava indo para implementar um pool de objetos para o meu sistema de partículas em Java, então eu encontrei isso na Wikipedia. Para reformular, ele diz que os pools de objetos não valem a pena usar em linguagens gerenciadas como Java e C #, porque as alocações levam apenas dezenas de operações em comparação com centenas em linguagens não gerenciadas como C ++.
Mas como todos sabemos, todas as instruções podem prejudicar o desempenho do jogo. Por exemplo, um pool de clientes em um MMO: os clientes não entram e saem do pool muito rápido. Mas as partículas podem se renovar dezenas de vezes em um segundo.
A questão é: vale a pena usar um pool de objetos para partículas (especificamente aquelas que morrem e são recriadas rapidamente) em uma linguagem gerenciada?
fonte
Para Java, não é tão útil agrupar objetos *, pois o primeiro ciclo de GC para objetos que ainda estão por aí os reorganizará na memória, movendo-os para fora do espaço "Eden" e potencialmente perdendo a localidade espacial no processo.
Java oferece alocação rápida de burst usando um alocador seqüencial quando você aloca objetos rapidamente no espaço Eden. Essa estratégia de alocação sequencial é super rápida, mais rápida do que
malloc
em C, uma vez que apenas agrupa a memória já alocada de maneira sequencial direta, mas vem com a desvantagem de que você não pode liberar blocos individuais de memória. Também é um truque útil em C se você deseja alocar coisas super rápido para, por exemplo, uma estrutura de dados em que você não precise remover nada dela, basta adicionar tudo e depois usá-lo e jogar tudo fora mais tarde.Devido a essa desvantagem de não conseguir liberar objetos individuais, o Java GC, após um primeiro ciclo, copiará toda a memória alocada do espaço Eden para novas regiões de memória usando um alocador de memória mais lento e de uso geral que permite que a memória seja armazenada. ser liberado em pedaços individuais em um encadeamento diferente. Depois, pode jogar fora a memória alocada no espaço do Éden como um todo, sem se preocupar com objetos individuais que agora foram copiados e vivem em outro lugar na memória. Após esse primeiro ciclo do GC, seus objetos podem acabar sendo fragmentados na memória.
Como os objetos podem acabar sendo fragmentados após o primeiro ciclo do GC, os benefícios do agrupamento de objetos, principalmente para melhorar os padrões de acesso à memória (localidade de referência) e reduzir a sobrecarga de alocação / desalocação, são amplamente perdidos ... para que você obtenha uma melhor localidade de referência normalmente alocando novas partículas o tempo todo e usá-las enquanto ainda estão frescas no espaço Eden e antes que se tornem "velhas" e potencialmente dispersas na memória. No entanto, o que pode ser extremamente útil (como obter o desempenho rival do C em Java) é evitar o uso de objetos para suas partículas e agrupar dados primitivos antigos simples. Para um exemplo simples, em vez de:
Faça algo como:
Agora, para reutilizar a memória para partículas existentes, você pode fazer o seguinte:
Agora quando o
nth
partícula morrer, para permitir sua reutilização, empurre-a para a lista livre da seguinte maneira:Ao adicionar uma nova partícula, veja se é possível exibir um índice da lista gratuita:
Não é o código mais agradável de se trabalhar, mas com isso você poderá obter algumas simulações muito rápidas de partículas, pois o processamento seqüencial de partículas é muito amigável para o cache sempre, pois todos os dados de partículas sempre serão armazenados de forma contígua. Esse tipo de representante SoA também reduz o uso de memória, pois não precisamos nos preocupar com preenchimento, os metadados do objeto para reflexão / envio dinâmico e separa os campos quentes dos campos frios (por exemplo, não estamos necessariamente preocupados com dados campos como a cor de uma partícula durante a física passam, por isso seria um desperdício carregá-la em uma linha de cache apenas para não usá-la e despejá-la).
Para facilitar o trabalho do código, pode valer a pena escrever seus próprios contêineres redimensionáveis básicos que armazenam matrizes de flutuadores, matrizes de números inteiros e matrizes de booleanos. Novamente, você não pode usar genéricos e
ArrayList
aqui (pelo menos desde a última vez que verifiquei), pois isso requer objetos gerenciados por GC, não dados primitivos contíguos. Queremos usar uma matriz contígua deint
, por exemplo, matrizes não gerenciadas por GC,Integer
que não serão necessariamente contíguas depois de deixar o espaço Eden.Com matrizes de tipos primitivos, elas sempre são garantidas como contíguas e, portanto, você obtém a localidade de referência extremamente desejável (para o processamento seqüencial de partículas, isso faz muita diferença) e todos os benefícios que o pool de objetos se destina a fornecer. Com uma matriz de objetos, é algo análogo a uma matriz de ponteiros que começam a apontar para os objetos de maneira contígua, assumindo que você os alocou todos de uma vez no espaço Eden, mas após um ciclo de GC, podem estar apontando por todo o lado. coloque na memória.
fonte