
Perfis de memória no Unity
Ao analisar e aprimorar o desempenho do seu jogo para uma ampla gama de plataformas e dispositivos, você pode expandir sua base de jogadores e aumentar suas chances de sucesso.
Esta página fornece informações sobre duas ferramentas para analisar o uso de memória em sua aplicação no Unity: o módulo Profiler de Memória embutido e o pacote Profiler de Memória, um pacote Unity que você pode adicionar ao seu projeto.
As informações aqui são extraídas do e-book, Guia definitivo para perfis de jogos Unity, disponível para download gratuito. O e-book foi criado por especialistas em desenvolvimento de jogos, perfis e otimização, tanto externos quanto internos da Unity.
Continue lendo para aprender sobre perfis de memória no Unity.
Perfis de memória
Os perfis de memória são úteis para testar contra limitações de memória da plataforma de hardware, diminuindo o tempo de carregamento e falhas, e tornando seu projeto compatível com dispositivos mais antigos. Também pode ser relevante se você quiser melhorar o desempenho da CPU/GPU fazendo alterações que realmente aumentem o uso de memória. Está amplamente desconectado do desempenho em tempo de execução.
Existem duas maneiras de analisar o uso de memória em sua aplicação no Unity.
O módulo Profiler de Memória: Este é um módulo de profiler embutido que fornece informações básicas sobre onde sua aplicação usa memória.
O pacote Profiler de Memória: Este é um pacote Unity que você pode adicionar ao seu projeto. Ele adiciona uma janela adicional do Profiler de Memória ao Editor Unity, que você pode usar para analisar o uso de memória em sua aplicação com ainda mais detalhes. Você pode armazenar e comparar snapshots para encontrar fugas de memória ou analisar o layout de memória para identificar problemas de fragmentação.
Com essas ferramentas integradas, você pode monitorar o uso de memória, localizar áreas de um aplicativo onde o uso de memória é maior do que o esperado e encontrar e melhorar a fragmentação da memória.

Compreender e definir um orçamento de memória
Compreender e orçar as limitações de memória dos dispositivos-alvo é fundamental para o desenvolvimento multiplataforma. Ao projetar cenas e níveis, mantenha-se dentro do orçamento de memória definido para cada dispositivo-alvo. Ao definir limites e diretrizes, você pode garantir que seu aplicativo funcione bem dentro das especificações de hardware de cada plataforma.
Você pode encontrar as especificações de memória do dispositivo na documentação do desenvolvedor. Por exemplo, o console Xbox One é limitado a 5 GB de memória máxima disponível para jogos em execução em primeiro plano, de acordo com a documentação.
Também pode ser útil definir orçamentos de conteúdo em torno da complexidade de malhas e shaders, bem como para compressão de texturas. Todos esses fatores influenciam quanto de memória é alocada. Esses números orçamentários podem ser consultados durante o ciclo de desenvolvimento do projeto.
Determine os limites físicos de RAM
Cada plataforma-alvo tem um limite de memória e, uma vez que você o conhece, pode definir um orçamento de memória para seu aplicativo. Use o Profiler de Memória para olhar para um snapshot de captura. Os Recursos de Hardware (veja a imagem acima) mostram os tamanhos da Memória de Acesso Aleatório Física (RAM) e da Memória de Acesso Aleatório de Vídeo (VRAM). Esse número não leva em conta o fato de que nem todo aquele espaço pode estar disponível para uso. No entanto, fornece uma estimativa útil para começar a trabalhar.
É uma boa ideia cruzar as especificações de hardware para plataformas-alvo, pois os números exibidos aqui podem não mostrar sempre o quadro completo. O hardware do kit de desenvolvedor às vezes tem mais memória, ou você pode estar trabalhando com hardware que possui uma arquitetura de memória unificada.
Determine a especificação mínima de RAM
Identifique o hardware com a especificação mais baixa em termos de RAM para cada plataforma que você suporta e use isso para orientar sua decisão de orçamento de memória. Lembre-se de que nem toda aquela memória física pode estar disponível para uso. Por exemplo, um console pode ter um hipervisor em execução para suportar jogos mais antigos que podem usar parte da memória total. Pense em uma porcentagem (por exemplo, 80% do total) a ser utilizada. Para plataformas móveis, você também pode considerar dividir em múltiplos níveis de especificações para suportar melhor qualidade e recursos para aqueles com dispositivos de ponta.
Considere orçamentos por equipe para equipes maiores
Uma vez que você tenha um orçamento de memória definido, considere definir orçamentos de memória por equipe. Por exemplo, seus artistas de ambiente recebem uma certa quantidade de memória para usar em cada nível ou cena que é carregada, a equipe de áudio recebe alocação de memória para música e efeitos sonoros, e assim por diante.
É importante ser flexível com os orçamentos à medida que o projeto avança. Se uma equipe ficar muito abaixo do orçamento, atribua o excedente a outra equipe se isso puder melhorar as áreas do jogo que estão desenvolvendo.
Uma vez que você decida e defina orçamentos de memória para suas plataformas-alvo, o próximo passo é usar ferramentas de perfil para ajudá-lo a monitorar e rastrear o uso de memória em seu jogo.

Duas exibições com o módulo Profiler de memória
O módulo Profiler de memória fornece duas visualizações: Simples e Detalhada. Use a visualização Simples para obter uma visão geral do uso de memória para sua aplicação. Quando necessário, mude para a visualização Detalhada para aprofundar mais.
Simples
A figura de Memória Total Reservada é o “Total Rastreado pela Memória do Unity.” Inclui memória que o Unity reservou, mas não está usando no momento (essa figura é a Memória Total Usada).
A figura de Memória Usada do Sistema é o que o OS considera como estando em uso pela sua aplicação. Se essa figura algum dia mostrar 0, esteja ciente de que isso indica que o contador do Profiler não está implementado na plataforma que você está perfilando. Nesse caso, o melhor indicador a ser considerado é a Memória Total Reservada. Também é recomendado mudar para uma ferramenta de perfil de plataforma nativa para informações detalhadas de memória nesses casos.

Visualização detalhada no Profiler de memória
Para investigar quanto de memória é usado pelo seu executável, DLLs e a Máquina Virtual Mono, números de memória quadro a quadro não são suficientes. Use uma captura de instantâneo detalhada para aprofundar-se nesse tipo de detalhamento de memória.
Observação: A árvore de referência na visualização Detalhada do módulo Memory Profiler mostra apenas referências Nativas. Referências de objetos de tipos que herdam de UnityEngine.Object podem aparecer com o nome de suas shells gerenciadas. No entanto, elas podem aparecer apenas porque têm Objetos Nativos abaixo delas. Você não verá necessariamente nenhum tipo gerenciado. Vamos tomar como exemplo um objeto com um Texture2D em um de seus campos como referência. Usando esta visualização, você também não verá qual campo contém essa referência. Para esse tipo de detalhe, use o Pacote Memory Profiler.
Para determinar em um nível alto quando o uso de memória começa a se aproximar dos orçamentos da plataforma, use o seguinte cálculo "nas costas de um guardanapo":
Memória Usada pelo Sistema (ou Memória Total Reservada se Memória Usada mostrar 0) + buffer aproximado de memória não rastreada / memória total da Plataforma
Quando esse número começa a se aproximar de 100% do orçamento de memória da sua plataforma, use o pacote Memory Profiler para descobrir o porquê.
Muitos dos recursos do módulo Memory Profiler foram superados pelo pacote Memory Profiler, mas você ainda pode usar o módulo para complementar seus esforços de análise de memória.
Por exemplo:
- Para identificar alocações de GC: Embora esses apareçam no módulo, é mais fácil rastreá-los usando Project Auditor ou Profiling Profundo.
- Para olhar rapidamente o tamanho Usado/Reservado do heap
- análise de memória de shader
Lembre-se de perfilar no dispositivo que tem as especificações mais baixas para sua plataforma-alvo geral ao definir um orçamento de memória. Monitore de perto o uso de memória, mantendo seus limites-alvo em mente.
Você geralmente vai querer perfilar usando um sistema de desenvolvimento poderoso com muita memória disponível (espaço para armazenar grandes instantâneas de memória ou carregar e salvar essas instantâneas rapidamente é importante).
O perfil de memória é uma besta diferente em comparação com o perfil de CPU e GPU, pois pode incorrer em sobrecarga adicional de memória. Você pode precisar perfilar a memória em dispositivos de alta gama (com mais memória), mas preste atenção ao limite do orçamento de memória para a especificação de baixo custo.
Pontos a considerar ao perfilar o uso de memória:
- Configurações como níveis de qualidade, níveis gráficos e variantes de AssetBundle podem ter diferentes usos de memória em dispositivos mais poderosos. Por exemplo:
- O nível de qualidade e as configurações gráficas podem afetar o tamanho das RenderTextures usadas para mapas de sombra.
- A escala de resolução pode afetar o tamanho dos buffers de tela, RenderTextures e efeitos de pós-processamento.
- A configuração de qualidade de textura pode afetar o tamanho de todas as Texturas.
- O LOD máximo pode afetar Modelos e mais.
- Se você tiver variantes de AssetBundle como uma versão HD (Alta Definição) e uma versão SD (Definição Padrão) e escolher qual usar com base nas especificações do dispositivo, você também pode obter tamanhos de ativos diferentes com base em qual dispositivo você está perfilando.
- A Resolução de Tela do seu dispositivo alvo afetará o tamanho das RenderTextures usadas para efeitos de pós-processamento.
- A API Gráfica suportada de um dispositivo pode afetar o tamanho dos shaders com base nas variantes que são suportadas ou não pela API.
- Ter um sistema em camadas que usa diferentes Configurações de Qualidade, configurações de Nível Gráfico e variações de Asset Bundle é uma ótima maneira de poder direcionar uma gama mais ampla de dispositivos, por exemplo, carregando uma versão de Alta Definição de um AssetBundle em um dispositivo móvel de 4 GB e uma versão de Definição Padrão em um dispositivo de 2 GB. No entanto, leve em consideração as variações acima no uso de memória e certifique-se de testar ambos os tipos de dispositivos, bem como dispositivos com diferentes resoluções de tela ou APIs gráficas suportadas.
Observação: O Editor Unity geralmente sempre mostrará uma pegada de memória maior devido a objetos adicionais que são carregados do Editor e do Profiler. Ele pode até mostrar Memória de Ativos que não seria carregada na memória em uma compilação, como de Asset Bundles (dependendo do modo de simulação Addressables) ou Sprites e Atlases, ou para Ativos mostrados no Inspetor. Algumas das cadeias de referência também podem ser mais confusas no Editor.

O pacote Profiler de memória
O Profiler de Memória está atualmente em pré-visualização para Unity 2019 LTS ou mais recente, mas espera-se que seja verificado no Unity 2022 LTS.
Um grande benefício do pacote Memory Profiler é que, além de capturar objetos nativos (como o módulo Memory Profiler faz), ele também permite que você visualize Memória Gerenciada, salve e compare instantâneos, e explore o conteúdo da memória com ainda mais detalhes, com análises visuais do uso da sua memória.
Um instantâneo mostra as alocações de memória no motor, permitindo que você identifique rapidamente as causas do uso excessivo ou desnecessário de memória, rastreie vazamentos de memória ou veja a fragmentação do heap.
Após instalar o pacote Memory Profiler, abra-o clicando em Janela > Análise > Profiler de Memória.
A barra de menu superior do Profiler de Memória permite que você altere o alvo de seleção do jogador e capture ou importe instantâneos.
Observação: Profile a memória no hardware de destino conectando o Profiler de Memória ao dispositivo remoto com o menu suspenso de seleção de alvo. A profilagem no Editor do Unity fornecerá números imprecisos devido a sobrecargas adicionadas pelo Editor e outras ferramentas.

Visualizações de instantâneos individuais ou para comparação
À esquerda da janela do Profiler de Memória está a área da Mesa de Trabalho. Use isso para gerenciar e abrir ou fechar instantâneos de memória salvos. Você também pode usar esta área para alternar entre as visualizações de Instantâneos Únicos e Comparar Instantâneos.
Semelhante ao Analisador de Perfil, o Profiler de Memória permite que você carregue dois conjuntos de dados (instantâneos de memória) para compará-los. Isso é especialmente útil ao observar como o uso de memória cresceu ao longo do tempo ou entre cenas e ao procurar vazamentos de memória.
O Profiler de Memória tem várias abas na janela principal que permitem que você aprofunde-se nos instantâneos de memória, incluindo Resumo, Objetos e Alocações, e Fragmentação. Vamos analisar cada uma dessas opções em detalhes.

A exibição Summary (Resumo)
Escolha esta visualização quando quiser obter uma visão rápida do uso de memória de um projeto. Ela também contém números úteis e importantes relacionados à memória para o instantâneo de memória capturado em questão. É perfeito para uma rápida olhada no que está acontecendo no momento em que um instantâneo foi tirado.

Tree Map gráfico
A visualização do Tree Map exibe uma divisão da memória usada por Objetos como um mapa de árvore gráfico que você pode explorar para descobrir o tipo de Objetos que consomem mais memória.

Mapa de árvore: Tabela filtrada
Abaixo da visualização do Tree Map há uma tabela filtrada que é atualizada para exibir a lista de objetos nas células de grade selecionadas.
O Tree Map mostra a memória atribuída a Objetos, seja Nativos ou Gerenciados. A memória de Objetos Gerenciados tende a ser ofuscada pela memória de Objetos Nativos, tornando mais difícil de identificar na visualização do mapa. Você pode ampliar o Tree Map para observar esses objetos, mas para inspecionar objetos menores, tabelas geralmente fornecem uma visão geral melhor. Clicar nas células do Tree Map filtrará a tabela abaixo para o tipo da seção e/ou selecionará o objeto específico de interesse na tabela.
Você pode rastrear quais itens referenciam objetos nesta lista e possivelmente quais campos de classe Gerenciada essas referências residem selecionando a linha da tabela ou a célula da grade do Tree Map que a representa, e então verificando a Seção de Referências no painel lateral de Detalhes. Se o painel lateral estiver oculto, você pode torná-lo visível através de um botão de alternância na parte superior direita da barra de ferramentas da janela.
Observação: O Tree Map mostra apenas Objetos na memória. Não é uma representação completa da memória rastreada. Isso é importante entender caso você perceba que os números da Visão Geral do Uso de Memória não são os mesmos que o total de Memória Rastreada.
Isso resulta do fato de que nem toda memória nativa está ligada a Objetos. Ela também pode consistir em Alocações Nativas não associadas a Objetos, como executáveis e DLLs, NativeArrays, e assim por diante. Conceitos ainda mais abstratos, como "Espaço de memória reservado, mas não utilizado", podem influenciar o total de Alocações Nativas.

Objects and Allocations
A visualização de Objetos e Alocações mostra uma tabela que pode ser alternada para filtrar com base em seleções prontas, como Todos os Objetos, Todos os Objetos Nativos, Todos os Objetos Gerenciados, Todas as Alocações Nativas e mais.
Você pode alternar a tabela inferior para exibir os Objetos, Alocações ou Regiões de Memória no intervalo selecionado. Como observado para a visualização do Mapa de Árvore, nem toda a memória está associada a Objetos, então as páginas de Todas as Regiões de Memória e Todas as Alocações Nativas podem fornecer uma imagem mais completa do seu uso de memória, onde as Regiões de Memória também incluem memória reservada, mas atualmente não utilizada.
Use isso a seu favor ao otimizar o uso de memória e visando empacotar a memória de forma mais eficiente para plataformas de hardware onde os orçamentos de memória são limitados.
Técnicas e fluxos de trabalho de perfilagem de memória
Carregue uma captura de instantâneo do Profiler de Memória e passe pela visualização do Mapa de Árvore para inspecionar as categorias, ordenadas do maior para o menor em tamanho de pegada de memória.
Os ativos do projeto são frequentemente os maiores consumidores de memória. Usando a visualização em Tabela, localize objetos de Textura, Malhas, Clips de Áudio, RenderTextures, Shaders e buffers pré-alocados. Todos esses são bons candidatos para otimização de memória.
Localizando vazamentos de memória
Um vazamento de memória geralmente acontece quando:
- Um objeto não é liberado manualmente da memória através do código
- Um objeto permanece na memória devido a uma referência não intencional
O modo Comparar do Profiler de Memória pode ajudar a encontrar vazamentos de memória comparando duas capturas de instantâneo ao longo de um período específico.
Um cenário comum de vazamento de memória em jogos Unity pode ocorrer após descarregar uma cena.
O pacote Profiler de Memória tem um fluxo de trabalho que o guia pelo processo de descobrir esses tipos de vazamentos usando o modo Comparar.
Localizando alocações de memória recorrentes ao longo da vida útil da aplicação
Através da comparação diferencial de múltiplas capturas de memória, você pode identificar a fonte de alocações contínuas de memória durante a vida útil da aplicação.
As seções a seguir listam algumas dicas para ajudar a identificar alocações de heap gerenciadas em seus projetos.

Como encontrar alocações de memória
O módulo Profiler de Memória no Profiler Unity representa alocações gerenciadas por quadro com uma linha vermelha. Isso deve ser 0 na maior parte do tempo, então qualquer pico nessa linha indica quadros que você deve investigar para alocações gerenciadas.

Exibição da Timeline no módulo Profiler de uso da CPU
A exibição da Linha do Tempo no módulo Profiler de uso da CPU mostra alocações, incluindo as gerenciadas, em rosa, facilitando a visualização e o foco nelas.

Pilhas de chamada de alocação
As pilhas de chamada de alocação fornecem uma maneira rápida de descobrir alocações de memória gerenciadas em seu código. Essas fornecerão os detalhes da pilha de chamada que você precisa com menos sobrecarga em comparação com o que a análise profunda normalmente adicionaria, e podem ser ativadas em tempo real usando o Profiler padrão.
As pilhas de chamada de alocação estão desativadas por padrão no Profiler. Para ativá-las, clique no botão Pilhas de Chamadas na barra de ferramentas principal da janela do Profiler. Altere a exibição de Detalhes para Dados Relacionados.
Observação: Se você estiver usando uma versão mais antiga do Unity (anterior ao suporte de pilhas de chamada de alocação), então análise profunda é uma boa maneira de obter pilhas de chamada completas para ajudar a encontrar alocações gerenciadas.
A amostra GC.Alloc selecionada na Hierarquia ou Hierarquia Bruta agora conterá suas pilhas de chamada. Você também pode ver as pilhas de chamada das amostras GC.Alloc na dica de seleção na Linha do Tempo.

Exibição da hierarquia no Profiler de uso da CPU
A exibição da Hierarquia no Profiler de uso da CPU permite que você clique nos cabeçalhos das colunas para usá-los como critérios de ordenação. Classificar por GC Alloc é uma ótima maneira de focar nessas alocações.
Auditor de Projetos
Auditor de Projetos é uma ferramenta experimental de análise estática. Ela faz muitas coisas úteis, várias das quais estão fora do escopo deste guia, mas pode produzir uma lista de cada linha de código em um projeto que causa uma alocação gerenciada, sem nunca precisar executar o projeto. É uma maneira muito eficiente de encontrar e investigar esse tipo de problema.
Otimizações de Memória e GC
Unity usa o coletor de lixo Boehm-Demers-Weiser, que para a execução do código do seu programa e só retoma a execução normal uma vez que seu trabalho está completo.
Esteja ciente de alocações de heap desnecessárias que podem causar picos de GC.
- Strings: Em C#, strings são tipos de referência, não tipos de valor. Isso significa que cada nova string será alocada na heap gerenciada, mesmo que seja usada apenas temporariamente. Reduza a criação ou manipulação desnecessária de strings. Evite analisar arquivos de dados baseados em string, como JSON e XML, e armazene dados em ScriptableObjects ou formatos como MessagePack ou Protobuf. Use a classe StringBuilder se você precisar construir strings em tempo de execução.
- Chamadas de função do Unity: Algumas funções da API do Unity criam alocações na heap, particularmente aquelas que retornam um array de objetos gerenciados. Armazene referências a arrays em vez de alocá-los no meio de um loop. Além disso, aproveite certas funções que evitam gerar lixo. Por exemplo, use GameObject.CompareTag em vez de comparar manualmente uma string com GameObject.tag (pois retornar uma nova string cria lixo).
- Boxing: Evite passar uma variável do tipo valor no lugar de uma variável do tipo referência. Isso cria um objeto temporário, e o lixo potencial que vem com isso converte implicitamente o tipo valor em um tipo objeto (por exemplo, int i = 123; object o = i). Em vez disso, tente fornecer substituições concretas com o tipo valor que você deseja passar. Generics também podem ser usados para essas substituições.
- Coroutines: Embora yield não produza lixo, criar um novo objeto WaitForSeconds o faz. Armazene e reutilize o objeto WaitForSeconds em vez de criá-lo na linha yield ou use yield return null.
- LINQ e Expressões Regulares: Ambos geram lixo devido ao boxing nos bastidores. Evite LINQ e Expressões Regulares se o desempenho for um problema. Escreva loops for e use listas como uma alternativa para criar novos arrays.
- Coleções Genéricas e outros tipos gerenciados: Não declare e preencha uma Lista ou coleção a cada frame no Update (por exemplo, uma lista de inimigos dentro de um certo raio do jogador). Em vez disso, faça da Lista um membro do MonoBehaviour e inicialize-a no Start. Simplesmente esvazie a coleção com Clear a cada frame antes de usá-la.
Coleta de lixo de tempo sempre que possível
Se você tiver certeza de que uma pausa na coleta de lixo não afetará um ponto específico do seu jogo, você pode acionar a coleta de lixo com System.GC.Collect.
Veja Entendendo a Gerência Automática de Memória para exemplos de como usar isso a seu favor.
Use o Coletor de Lixo Incremental para dividir a carga de trabalho do GC
Em vez de criar uma única interrupção longa durante a execução do seu programa, a coleta de lixo incremental usa múltiplas interrupções mais curtas que distribuem a carga de trabalho ao longo de muitos frames. Se a coleta de lixo estiver causando uma taxa de quadros irregular, experimente esta opção para ver se pode reduzir o problema de picos de GC. Use o Analisador de Perfil para verificar seu benefício para sua aplicação.
Observe que usar o GC em modo Incremental adiciona barreiras de leitura-escrita a algumas chamadas de C#, o que vem com uma sobrecarga que pode somar até ~1 ms por frame de sobrecarga de chamada de script. Para desempenho ideal, é ideal não ter GC Allocs nos loops principais de jogabilidade para que você não precise do GC Incremental para uma taxa de quadros suave e possa ocultar o GC.Collect onde um usuário não notará, por exemplo, ao abrir o menu ou carregar um novo nível.
Para saber mais sobre o Memory Profiler, confira os seguintes recursos:

Baixe o e-book Guia definitivo para perfilagem de jogos Unity gratuitamente para obter todas as dicas e melhores práticas.