O que você está procurando?
Hero background image

Dicas de perfil de desempenho para desenvolvedores de jogos

Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Um desempenho suave é essencial para criar experiências de jogo imersivas para os jogadores. Ao perfilar 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 descreve um fluxo de trabalho geral de perfil para desenvolvedores de jogos. É um trecho do e-book, Guia definitivo para perfilagem de jogos Unity, disponível para download gratuito. O e-book foi criado por especialistas em desenvolvimento de jogos da Unity, tanto externos quanto internos, em perfilagem e otimização.

Continue lendo para aprender sobre metas úteis a serem definidas com a perfilagem, gargalos de desempenho comuns, como estar limitado pela CPU ou pela GPU, e como identificar e investigar essas situações em mais detalhes.

Gráfico da taxa de quadros de um FPS

Defina um orçamento de quadros

Medir a taxa de quadros do seu jogo em quadros por segundo (fps) não é ideal para oferecer experiências consistentes para seus jogadores. Considere o seguinte cenário simplificado:

Durante a execução, seu jogo renderiza 59 quadros em 0,75 segundos. No entanto, o próximo quadro leva 0,25 segundos para ser renderizado. A taxa média de quadros entregue de 60 fps parece boa, mas na realidade os jogadores notarão um efeito de gagueira, já que o último quadro leva um quarto de segundo para ser renderizado.

Esta é uma das razões pelas quais é importante ter como objetivo um orçamento de tempo específico por quadro. Isso fornece a você uma meta sólida para trabalhar ao perfilar e otimizar seu jogo e, em última análise, cria uma experiência mais suave e consistente para seus jogadores.

Cada quadro terá um orçamento de tempo baseado na sua taxa de quadros alvo. Um aplicativo com alvo de 30 fps deve sempre levar menos de 33,33 ms por quadro (1000 ms / 30 fps). Da mesma forma, um alvo de 60 fps deixa 16,66 ms por quadro (1000 ms / 60 fps).

Você pode exceder esse orçamento durante sequências não interativas, por exemplo, ao exibir menus de UI ou carregamento de cena, mas não durante o jogo. Mesmo um único quadro que exceda o orçamento de quadro alvo causará travamentos.

Nota: Uma taxa de quadros consistentemente alta em jogos de VR é essencial para evitar causar náusea ou desconforto aos jogadores. Sem isso, você corre o risco de ser rejeitado pelo detentor da plataforma durante a certificação do seu jogo.

Quadros por segundo: Uma métrica enganosa

Uma maneira comum que os jogadores medem o desempenho é com a taxa de quadros, ou quadros por segundo. No entanto, é recomendado que você use o tempo de quadro em milissegundos em vez disso. Para entender por que, olhe para o gráfico acima de fps versus tempo de quadro.

Considere esses números:

1000 ms/sec / 900 fps = 1,111 ms por quadro
1000 ms/sec / 450 fps = 2,222 ms por quadro

1000 ms/sec / 60 fps = 16,666 ms por quadro
1000 ms/sec / 56,25 fps = 17,777 ms por quadro

Se seu aplicativo estiver rodando a 900 fps, isso se traduz em um tempo de quadro de 1,111 milissegundos por quadro. A 450 fps, isso é 2,222 milissegundos por quadro. Isso representa uma diferença de apenas 1,111 milissegundos por quadro, mesmo que a taxa de quadros pareça cair pela metade.

Se você olhar as diferenças entre 60 fps e 56,25 fps, isso se traduz em 16,666 milissegundos por quadro e 17,777 milissegundos por quadro, respectivamente. Isso também representa 1,111 milissegundos extras por quadro, mas aqui, a queda na taxa de quadros parece muito menos dramática em termos percentuais.

É por isso que os desenvolvedores usam o tempo médio de quadro para avaliar a velocidade do jogo em vez de fps.

Não se preocupe com fps a menos que você caia abaixo da sua taxa de quadros alvo. Concentre-se no tempo de quadro para medir quão rápido seu jogo está rodando, e então mantenha-se dentro do seu orçamento de quadros.

Leia o artigo original, “Robert Dunlop’s fps versus frame time,” para mais informações.

Desafios para dispositivos móveis

Desafios para dispositivos móveis

O controle térmico é uma das áreas mais importantes a otimizar ao desenvolver aplicativos para dispositivos móveis. Se a CPU ou GPU gastarem muito tempo trabalhando em plena carga devido a um design ineficiente, esses chips ficarão quentes. Para evitar danos aos chips (e potencialmente queimar as mãos de um jogador!), o sistema operacional reduzirá a velocidade do clock do dispositivo para permitir que ele esfrie, causando gagueira nos quadros e uma experiência de usuário ruim. Essa redução de desempenho é conhecida como estrangulamento térmico.

Taxas de quadros mais altas e aumento na execução de código (ou operações de acesso à DRAM) levam a um aumento no consumo de bateria e geração de calor. Um desempenho ruim também pode cortar segmentos inteiros de dispositivos móveis de baixo desempenho, o que pode levar a oportunidades de mercado perdidas e, portanto, a vendas mais baixas.

Ao enfrentar o problema térmico, considere o orçamento que você tem para trabalhar como um orçamento de sistema.

Combata o estrangulamento térmico e o consumo de bateria aproveitando uma técnica de perfilagem inicial para otimizar seu jogo desde o início. Ajuste as configurações do seu projeto para o hardware da plataforma alvo para combater problemas térmicos e de consumo de bateria.

Ajuste os orçamentos de quadros em dispositivos móveis

Deixar um tempo ocioso de quadro de cerca de 35% é a recomendação típica para combater problemas térmicos do dispositivo durante tempos de jogo prolongados. Isso dá tempo para os chips móveis esfriarem e ajuda a prevenir o consumo excessivo de bateria. Usando um tempo de quadro alvo de 33,33 ms por quadro (para 30 fps), um orçamento típico de quadros para dispositivos móveis será de aproximadamente 22 ms por quadro.

O cálculo é assim: (1000 ms / 30) * 0.65 = 21.66 ms

Para alcançar 60 fps em dispositivos móveis usando o mesmo cálculo, seria necessário um tempo de quadro alvo de (1000 ms / 60) * 0.65 = 10.83 ms. Isso é difícil de alcançar em muitos dispositivos móveis e drenaria a bateria duas vezes mais rápido do que mirar em 30 fps. Por essas razões, a maioria dos jogos móveis mira em 30 fps em vez de 60. Use Application.targetFrameRate para controlar essa configuração e consulte a seção “Definir um orçamento de quadro” no e-book para mais detalhes sobre o tempo de quadro.

A escalabilidade de frequência em chips móveis pode dificultar a identificação das alocações do seu orçamento de tempo ocioso de quadro ao fazer o perfil. Suas melhorias e otimizações podem ter um efeito positivo líquido, mas o dispositivo móvel pode estar reduzindo a frequência e, como resultado, funcionando mais frio. Use ferramentas personalizadas como FTrace ou Perfetto para monitorar as frequências dos chips móveis, o tempo ocioso e a escalabilidade antes e depois das otimizações.

Desde que você permaneça dentro do seu orçamento total de tempo de quadro para o fps alvo (33,33 ms para 30 fps) e veja seu dispositivo trabalhando menos ou registrando temperaturas mais baixas para manter essa taxa de quadros, então você está no caminho certo.

Outra razão para adicionar espaço de manobra ao orçamento de quadro em dispositivos móveis é levar em conta as flutuações de temperatura do mundo real. Em um dia quente, um dispositivo móvel aquecerá e terá dificuldade em dissipar calor, o que pode levar a estrangulamento térmico e desempenho ruim do jogo. Reservar uma porcentagem do orçamento de quadro ajudará a evitar esses tipos de cenários.

Reduza as operações de acesso à memória

Reduza as operações de acesso à memória

O acesso à DRAM é tipicamente uma operação que consome muita energia em dispositivos móveis. O conselho de otimização da Arm para conteúdo gráfico em dispositivos móveis diz que o acesso à memória LPDDR4 custa aproximadamente 100 picojoules por byte.

Reduza o número de operações de acesso à memória por quadro por:

  • Reduzir a taxa de quadros
  • Reduzir a resolução da tela onde for possível
  • Usar malhas mais simples com contagem de vértices reduzida e precisão de atributos
  • Usar compressão de textura e mipmapping

Quando você precisa se concentrar em dispositivos que utilizam hardware Arm ou Arm Mali, a ferramenta Arm Mobile Studio (especificamente, Streamline Performance Analyzer) inclui alguns ótimos contadores de desempenho para identificar problemas de largura de banda de memória. Os contadores são listados e explicados para cada geração de GPU Arm, por exemplo, Mali-G78. Observe que a profilagem de GPU do Mobile Studio requer Arm Mali.

Estabeleça níveis de hardware para benchmarking

Além de usar ferramentas de perfilagem específicas da plataforma, estabeleça níveis ou um dispositivo de especificação mais baixa para cada plataforma e nível de qualidade que você deseja suportar, em seguida, perfil e otimize o desempenho para cada uma dessas especificações.

Como exemplo, se você está visando plataformas móveis, pode decidir suportar três níveis com controles de qualidade que ativam ou desativam recursos com base no hardware alvo. Você então otimiza para a especificação de dispositivo mais baixa em cada nível. Como outro exemplo, se você está desenvolvendo um jogo para PlayStation 4 e PlayStation 5, certifique-se de fazer a profilagem em ambos.

Para um guia completo de otimização móvel, dê uma olhada em Otimize o desempenho do seu jogo móvel. Este e-book tem muitas dicas e truques que ajudarão você a reduzir o estrangulamento térmico e aumentar a vida útil da bateria para dispositivos móveis que executam seus jogos.

Da perfilagem de alto a baixo nível

Uma abordagem de cima para baixo funciona bem ao fazer a profilagem, começando com a Profilagem Profunda desativada. Use esta abordagem de alto nível para coletar dados e anotar quais cenários causam alocações gerenciadas indesejadas ou muito tempo de CPU nas áreas do seu loop de jogo principal.

Você precisará primeiro coletar pilhas de chamadas para marcadores GC.Alloc. Se você não estiver familiarizado com esse processo, encontre algumas dicas e truques na seção "Localizando alocações de memória recorrentes ao longo da vida útil da aplicação" em Guia definitivo para perfilagem de jogos Unity.

Se as pilhas de chamadas relatadas não forem detalhadas o suficiente para rastrear a origem das alocações ou outras lentidões, você pode então realizar uma segunda sessão de perfilagem com a Profilagem Profunda ativada para encontrar a origem das alocações.

Ao coletar notas sobre os "infratores" do tempo de quadro, certifique-se de anotar como eles se comparam em relação ao resto do quadro. Esse impacto relativo será afetado ao ativar a Profilagem Profunda.

Leia mais sobre a profilagem profunda em Guia definitivo para perfilagem de jogos Unity.

Perfilagem precoce

Os melhores ganhos da profilagem são obtidos quando você começa cedo no ciclo de desenvolvimento do seu projeto.

Perfilar cedo e frequentemente para que você e sua equipe entendam e memorizem uma "assinatura de desempenho" para o projeto. Se o desempenho despencar, você poderá facilmente identificar quando as coisas dão errado e corrigir o problema.

Os resultados de perfilamento mais precisos sempre vêm de executar e perfilar builds em dispositivos-alvo, juntamente com o uso de ferramentas específicas da plataforma para investigar as características de hardware de cada plataforma. Essa combinação fornecerá uma visão holística do desempenho do aplicativo em todos os seus dispositivos-alvo.

Fluxograma para perfis

Identificar problemas de desempenho

Baixe a versão PDF imprimível deste gráfico aqui.

Em algumas plataformas, determinar se seu aplicativo é limitado pela CPU ou pela GPU é fácil. Por exemplo, ao executar um jogo iOS a partir do Xcode, o painel de fps mostra um gráfico de barras com o tempo total da CPU e da GPU, para que você possa ver qual é o maior. O tempo da CPU inclui o tempo gasto esperando pelo VSync, que está sempre habilitado em dispositivos móveis.

No entanto, em algumas plataformas, pode ser desafiador obter dados de tempo da GPU. Felizmente, o Profiler do Unity mostra informações suficientes para identificar a localização dos gargalos de desempenho. O fluxograma acima ilustra o processo inicial de perfilamento, com as seções seguintes fornecendo informações detalhadas sobre cada etapa. Eles também apresentam capturas do Profiler de projetos reais do Unity para ilustrar os tipos de coisas a serem observadas.

Para obter uma visão holística de toda a atividade da CPU, incluindo quando está esperando pela GPU, use a visão da linha do tempo no módulo da CPU do Profiler. Familiarize-se com os marcadores comuns do Profiler para interpretar as capturas corretamente. Alguns dos marcadores do Profiler podem aparecer de forma diferente dependendo da sua plataforma-alvo, então passe um tempo explorando as capturas do seu jogo em cada uma das suas plataformas-alvo para ter uma noção de como é uma captura "normal" para o seu projeto.

O desempenho de um projeto é limitado pelo chip e/ou thread que leva mais tempo. Essa é a área onde você deve concentrar seus esforços de otimização. Por exemplo, imagine um jogo com um orçamento de tempo de quadro alvo de 33,33 ms e VSync habilitado:

  • Se o tempo de quadro da CPU (excluindo VSync) é de 25 ms e o tempo da GPU é de 20 ms, sem problemas! Você está limitado pela CPU, mas tudo está dentro do orçamento, e otimizar as coisas não melhorará a taxa de quadros (a menos que você consiga que tanto a CPU quanto a GPU fiquem abaixo de 16,66 ms e suba para 60 fps).
  • Se o tempo de quadro da CPU é de 40 ms e o da GPU é de 20 ms, você está limitado pela CPU e precisará otimizar o desempenho da CPU. Otimizar o desempenho da GPU não ajudará; na verdade, você pode querer mover parte do trabalho da CPU para a GPU, por exemplo, usando shaders de computação em vez de código C# para algumas coisas, para equilibrar as coisas.
  • Se o tempo de quadro da CPU é de 20 ms e o da GPU é de 40 ms, você está limitado pela GPU e precisa otimizar o trabalho da GPU.
  • Se a CPU e a GPU estão ambas em 40 ms, você está limitado por ambas e precisará otimizar ambas abaixo de 33,33 ms para alcançar 30 fps.

Veja esses recursos que exploram mais sobre estar limitado pela CPU ou pela GPU:

Você está dentro do intervalo de processamento de quadros?

Você está dentro do intervalo de processamento de quadros?

Perfilando e otimizando seu projeto cedo e frequentemente durante o desenvolvimento, você ajudará a garantir que todas as threads da CPU da sua aplicação e o tempo de quadro geral da GPU estejam dentro do orçamento de quadro.

Acima está uma imagem de uma captura do Profiler de um jogo móvel Unity desenvolvido por uma equipe que fez perfilamento e otimização contínuos. O jogo tem como alvo 60 fps em telefones móveis de alta especificação e 30 fps em telefones de especificação média/baixa, como o que está nesta captura.

Note como quase metade do tempo no quadro selecionado é ocupada pelo marcador Profiler amarelo WaitForTargetfps. A aplicação definiu Application.targetFrameRate para 30 fps, e o VSync está habilitado. O trabalho de processamento real na thread principal termina em torno da marca de 19 ms, e o restante do tempo é gasto esperando que o restante dos 33,33 ms passe antes de começar o próximo quadro. Embora esse tempo seja representado por um marcador do Profiler, a thread principal da CPU está essencialmente ociosa durante esse tempo, permitindo que a CPU esfrie e usando um mínimo de energia da bateria.

O marcador a ser observado pode ser diferente em outras plataformas ou se o VSync estiver desativado. O importante é verificar se a thread principal está rodando dentro do seu orçamento de quadro ou exatamente no seu orçamento de quadro, com algum tipo de marcador que indique que a aplicação está esperando pelo VSync e se as outras threads têm algum tempo ocioso.

O tempo ocioso é representado por marcadores do Profiler cinzas ou amarelos. A captura de tela acima mostra que a thread de renderização está ociosa em Gfx.WaitForGfxCommandsFromMainThread, o que indica momentos em que terminou de enviar chamadas de desenho para a GPU em um quadro e está esperando por mais solicitações de chamadas de desenho da CPU no próximo. Da mesma forma, embora a thread Job Worker 0 passe algum tempo em Canvas.GeometryJob, a maior parte do tempo está ociosa. Estes são todos sinais de uma aplicação que está confortavelmente dentro do orçamento de quadro.

Se o seu jogo estiver dentro do orçamento de quadros

Se você estiver dentro do orçamento de quadros, incluindo quaisquer ajustes feitos no orçamento para contabilizar o uso da bateria e o aquecimento térmico, você terminou a análise de desempenho até a próxima vez – parabéns. Considere executar o Profiler de Memória para garantir que o aplicativo também esteja dentro do seu orçamento de memória.

A imagem acima mostra um jogo rodando confortavelmente dentro do orçamento de ~22 ms necessário para 30 fps. Observe o WaitForTargetfps preenchendo o tempo da thread principal até o VSync e os tempos ociosos cinzas na thread de renderização e na thread de trabalho. Observe também que o intervalo de VBlank pode ser observado olhando os tempos de término de Gfx.Present quadro a quadro, e que você pode traçar uma escala de tempo na área da Linha do Tempo ou na régua de Tempo na parte superior para medir de um desses para o próximo.

Vinculado à CPU

Limitado pela CPU

Se o seu jogo não estiver dentro do orçamento de quadros da CPU, o próximo passo é investigar qual parte da CPU é o gargalo – em outras palavras, qual thread está mais ocupada. O objetivo da análise de desempenho é identificar gargalos como alvos para otimização; se você confiar em suposições, pode acabar otimizando partes do jogo que não são gargalos, resultando em pouca ou nenhuma melhoria no desempenho geral. Algumas "otimizações" podem até piorar o desempenho geral do seu jogo.

É raro que toda a carga de trabalho da CPU seja o gargalo. CPUs modernas têm um número de núcleos diferentes, capazes de realizar trabalho de forma independente e simultânea. Diferentes threads podem ser executadas em cada núcleo da CPU. Um aplicativo Unity completo usa uma variedade de threads para diferentes propósitos, mas as threads que são as mais comuns para encontrar problemas de desempenho são:

  • A thread principal: É aqui que toda a lógica/scripts do jogo realizam seu trabalho por padrão e onde a maior parte do tempo é gasto em recursos e sistemas como física, animação, UI e renderização.
  • A thread de renderização: Durante o processo de renderização, a thread principal examina a cena e realiza a culling da câmera, ordenação de profundidade e agrupamento de chamadas de desenho, resultando em uma lista de coisas a serem renderizadas. Esta lista é passada para a thread de renderização, que a traduz da representação interna agnóstica da plataforma da Unity para as chamadas específicas da API gráfica necessárias para instruir a GPU em uma plataforma particular.
  • As threads de trabalho do Job: Os desenvolvedores podem fazer uso do Sistema de Jobs C# para agendar certos tipos de trabalho para serem executados em threads de trabalho, o que reduz a carga de trabalho na thread principal. Alguns dos sistemas e recursos do Unity também utilizam o sistema de trabalho, como física, animação e renderização.

Thread principal

A imagem acima mostra como as coisas podem parecer em um projeto que está preso ao thread principal. Este projeto está rodando em um Meta Quest 2, que normalmente visa orçamentos de quadro de 13,88 ms (72 fps) ou até mesmo 8,33 ms (120 fps), porque altas taxas de quadros são importantes para evitar enjoo em dispositivos VR. No entanto, mesmo que este jogo estivesse visando 30 fps, é claro que este projeto está em apuros.

Embora o thread de renderização e os threads de trabalho pareçam semelhantes ao exemplo que está dentro do orçamento de quadro, o thread principal está claramente ocupado com trabalho durante todo o quadro. Mesmo considerando a pequena quantidade de sobrecarga do Profiler no final do quadro, o thread principal está ocupado por mais de 45 ms, o que significa que este projeto alcança taxas de quadros de menos de 22 fps. Não há marcador que mostre o thread principal esperando ocioso pelo VSync; ele está ocupado durante todo o quadro.

A próxima etapa da investigação é identificar as partes do quadro que levam mais tempo e entender por que isso acontece. Neste quadro, PostLateUpdate.FinishFrameRendering leva 16,23 ms, mais do que todo o orçamento do quadro. Uma inspeção mais próxima revela que há cinco instâncias de um marcador chamado Inl_RenderCameraStack, indicando que há cinco câmeras ativas e renderizando a cena. Como cada câmera no Unity invoca todo o pipeline de renderização, incluindo culling, sorting e batching, a tarefa de maior prioridade para este projeto é reduzir o número de câmeras ativas, idealmente para apenas uma.

BehaviourUpdate, o marcador que abrange todos os métodos MonoBehaviour Update(), leva 7,27 ms, e as seções magenta da linha do tempo indicam onde os scripts alocam memória gerenciada no heap. Mudar para a visualização de Hierarquia e filtrar digitando GC.Alloc na barra de pesquisa mostra que alocar essa memória leva cerca de 0,33 ms neste quadro. No entanto, essa é uma medição imprecisa do impacto que as alocações de memória têm no desempenho da sua CPU.

Marcadores GC.Alloc não são realmente cronometrados medindo o tempo de um ponto de Início a um ponto de Fim. Para manter sua sobrecarga pequena, eles são registrados apenas como seu carimbo de tempo de Início, mais o tamanho de sua alocação. O Profiler atribui uma quantidade mínima de tempo a eles para garantir que sejam visíveis. A alocação real pode levar mais tempo, especialmente se um novo intervalo de memória precisar ser solicitado ao sistema. Para ver o impacto mais claramente, coloque marcadores do Profiler ao redor do código que faz a alocação, e na profilagem profunda, os intervalos entre as amostras GC.Alloc coloridas de magenta na visualização da Linha do Tempo fornecem alguma indicação de quanto tempo elas podem ter levado.

Além disso, alocar nova memória pode ter efeitos negativos no desempenho que são mais difíceis de medir e atribuir diretamente a eles:

  • Solicitar nova memória do sistema pode afetar o orçamento de energia em um dispositivo móvel, o que pode levar o sistema a desacelerar a CPU ou GPU.
  • A nova memória provavelmente precisa ser carregada no Cache L1 da CPU e, assim, empurra as linhas de Cache existentes para fora.
  • A Coleta de Lixo Incremental ou Síncrona pode ser acionada diretamente ou com um atraso à medida que o espaço livre existente na Memória Gerenciada é eventualmente excedido.

No início do quadro, quatro instâncias de Physics.FixedUpdate somam 4,57 ms. Mais tarde, LateBehaviourUpdate (chamadas para MonoBehaviour.LateUpdate()) leva 4 ms, e Animadores contabilizam cerca de 1 ms.

Para garantir que este projeto atinja seu orçamento e taxa de quadros desejados, todas essas questões da thread principal precisam ser investigadas para encontrar otimizações adequadas. Os maiores ganhos de desempenho serão obtidos otimizando as coisas que levam mais tempo.

As seguintes áreas costumam ser lugares frutíferos para procurar otimizações em projetos que estão limitados pela thread principal:

  • Física
  • atualizações de script MonoBehaviour
  • alocação e/ou coleta de lixo
  • culling e renderização da câmera
  • agregação de chamadas de desenho ruim
  • atualizações de UI, layouts e reconstruções
  • Animação

Dependendo do problema que você deseja investigar, outras ferramentas também podem ser úteis:

  • Para scripts MonoBehaviour que levam muito tempo, mas não mostram exatamente por que esse é o caso, adicione Marcadores de Profiler ao código ou tente a profilagem profunda para ver toda a pilha de chamadas.
  • Para scripts que alocam memória gerenciada, ative Pilhas de Chamadas de Alocação para ver exatamente de onde vêm as alocações. Alternativamente, ative a Profilagem Profunda ou use o Auditor de Projetos, que mostra problemas de código filtrados por memória, para que você possa identificar todas as linhas de código que resultam em alocações gerenciadas.
  • Use o Depurador de Quadros para investigar as causas da agregação de chamadas de desenho ruim.

Para dicas abrangentes sobre como otimizar seu jogo, baixe esses guias de especialistas da Unity gratuitamente:

Limitado pela CPU: Thread de renderização

Limitado pela CPU: Thread de renderização

A captura de tela acima é de um projeto que está limitado pelo seu thread de renderização. Este é um jogo de console com uma perspectiva isométrica e um orçamento de quadro alvo de 33,33 ms.

A captura do Profiler mostra que antes que a renderização possa começar no quadro atual, o thread principal espera pelo thread de renderização, como indicado pelo marcador Gfx.WaitForPresentOnGfxThread. O thread de renderização ainda está enviando comandos de chamada de desenho do quadro anterior e não está pronto para aceitar novas chamadas de desenho do thread principal; o thread de renderização está gastando tempo em Camera.Render.

Você pode perceber a diferença entre marcadores relacionados ao quadro atual e marcadores de outros quadros, porque os últimos aparecem mais escuros. Você também pode ver que, uma vez que o thread principal consegue continuar e começar a emitir chamadas de desenho para o thread de renderização processar, o thread de renderização leva mais de 100 ms para processar o quadro atual, o que também cria um gargalo durante o próximo quadro.

Uma investigação mais aprofundada mostrou que este jogo tinha uma configuração de renderização complexa, envolvendo nove câmeras diferentes e muitos passes extras causados por shaders de substituição. O jogo também estava renderizando mais de 130 luzes pontuais usando um caminho de renderização para frente, o que pode adicionar várias chamadas de desenho transparentes adicionais para cada luz. No total, esses problemas combinados criaram mais de 3000 chamadas de desenho por quadro.

As seguintes são causas comuns a investigar para projetos que estão limitados pelo thread de renderização:

  • Agrupamento de chamadas de desenho ruim, particularmente em APIs gráficas mais antigas, como OpenGL ou DirectX 11
  • Câmeras demais. A menos que você esteja fazendo um jogo multiplayer de tela dividida, as chances são de que você deve ter apenas uma Câmera ativa.
  • Culling ruim, resultando em muitas coisas sendo desenhadas. Investigue as dimensões do frustum da sua Câmera e as máscaras de camada de culling. Considere habilitar o Culling de Oclusão. Talvez até crie seu próprio sistema simples de culling de oclusão com base no que você sabe sobre como os objetos estão dispostos em seu mundo. Veja quantos objetos que projetam sombra existem na cena – o culling de sombra acontece em um passe separado do culling "regular".

O módulo Profiler de Renderização mostra uma visão geral do número de lotes de chamadas de desenho e chamadas de SetPass a cada quadro. A melhor ferramenta para investigar quais lotes de chamadas de desenho sua thread de renderização está emitindo para a GPU é o Depurador de Quadro.

Limitado pela CPU: Threads de trabalho

Limitado pela CPU: Threads de trabalho

Projetos vinculados a threads de CPU diferentes da principal ou de renderização não são tão comuns. No entanto, isso pode ocorrer se seu projeto usar o Data-Oriented Technology Stack (DOTS), especialmente se o trabalho for movido da thread principal para threads de trabalho usando o Sistema de Trabalho C#.

A captura vista acima é do modo Play no Editor, mostrando um projeto DOTS executando uma simulação de fluido de partículas na CPU.

Parece um sucesso à primeira vista. As threads de trabalho estão compactadas com trabalhos compilados com Burst, indicando que uma grande quantidade de trabalho foi movida da thread principal. Normalmente, essa é uma decisão sensata.

No entanto, neste caso, o tempo de quadro de 48,14 ms e o marcador cinza WaitForJobGroupID de 35,57 ms na thread principal são sinais de que nem tudo está bem. WaitForJobGroupID indica que a thread principal agendou trabalhos para serem executados de forma assíncrona em threads de trabalho, mas precisa dos resultados desses trabalhos antes que as threads de trabalho tenham terminado de executá-los. Os marcadores azuis do Profiler abaixo de WaitForJobGroupID mostram a thread principal executando trabalhos enquanto espera, na tentativa de garantir que os trabalhos terminem mais cedo.

Embora os trabalhos sejam compilados com Burst, eles ainda estão fazendo muito trabalho. Talvez a estrutura de consulta espacial usada por este projeto para encontrar rapidamente quais partículas estão próximas umas das outras deva ser otimizada ou trocada por uma estrutura mais eficiente. Ou, os trabalhos de consulta espacial podem ser agendados para o final do quadro em vez do início, com os resultados não sendo necessários até o início do próximo quadro. Talvez este projeto esteja tentando simular muitas partículas. Uma análise mais aprofundada do código dos trabalhos é necessária para encontrar a solução, então adicionar marcadores de Profiler mais detalhados pode ajudar a identificar suas partes mais lentas.

Os trabalhos em seu projeto podem não estar tão paralelizados quanto neste exemplo. Talvez você tenha apenas um trabalho longo sendo executado em uma única thread de trabalho. Isso é aceitável, desde que o tempo entre o agendamento do trabalho e o tempo que precisa ser concluído seja longo o suficiente para que o trabalho seja executado. Se não for, você verá a thread principal travar enquanto espera o trabalho ser concluído, como na captura de tela acima.

Causas comuns de pontos de sincronização e gargalos de threads de trabalho incluem:

  • Trabalhos que não estão sendo compilados pelo compilador Burst
  • Trabalhos de longa duração em uma única thread de trabalho em vez de serem paralelizados em várias threads de trabalho
  • Tempo insuficiente entre o ponto no quadro quando um trabalho é agendado e o ponto quando o resultado é necessário
  • Múltiplos "pontos de sincronização" em um quadro, que exigem que todos os trabalhos sejam concluídos imediatamente

Você pode usar o Eventos de Fluxo recurso na visualização da Linha do Tempo do módulo Profiler de Uso da CPU para investigar quando os trabalhos são agendados e quando seus resultados são esperados pela thread principal. Para mais informações sobre como escrever código DOTS eficiente, consulte o guia Melhores Práticas DOTS.

Dependente da GPU

Dependente da GPU

Seu aplicativo está limitado pela GPU se a thread principal gastar muito tempo em marcadores do Profiler, como Gfx.WaitForPresentOnGfxThread, e sua thread de renderização exibir simultaneamente marcadores como Gfx.PresentFrame ou .WaitForLastPresent.

A captura a seguir foi feita em um Samsung Galaxy S7, usando a API gráfica Vulkan. Embora algum tempo gasto em Gfx.PresentFrame neste exemplo possa estar relacionado à espera pelo VSync, a extrema duração deste marcador do Profiler indica que a maior parte desse tempo é gasta esperando a GPU terminar de renderizar o quadro anterior.

Neste jogo, certos eventos de jogabilidade acionaram o uso de um shader que triplicou o número de chamadas de desenho renderizadas pela GPU. Questões comuns a investigar ao perfilar o desempenho da GPU incluem:

  • Efeitos de pós-processamento em tela cheia caros, incluindo culpados comuns como Oclusão Ambiental e Bloom
  • Shaders de fragmento caros causados por:
  • Lógica de ramificação
  • Uso de precisão de ponto flutuante total em vez de precisão de meio ponto flutuante
  • Uso excessivo de registradores que afetam a ocupação da onda das GPUs
  • Desenho excessivo na fila de renderização Transparente causado por UI ineficiente, sistemas de partículas ou efeitos de pós-processamento
  • Resoluções de tela excessivamente altas, como aquelas encontradas em displays 4K ou displays Retina em dispositivos móveis
  • Micro triângulos causados por geometria de malha densa ou falta de LODs, que é um problema particular em GPUs móveis, mas pode afetar GPUs de PC e console também.
  • Faltas de cache e largura de banda de memória GPU desperdiçada causadas por texturas não compactadas ou texturas de alta resolução sem mipmaps
  • Shaders de geometria ou tesselação, que podem ser executados várias vezes por quadro se sombras dinâmicas estiverem habilitadas

Se seu aplicativo parecer estar limitado pela GPU, você pode usar o Frame Debugger como uma maneira rápida de entender os lotes de chamadas de desenho que estão sendo enviados para a GPU. No entanto, esta ferramenta não pode apresentar informações específicas de tempo da GPU, apenas como a cena geral é construída.

A melhor maneira de investigar a causa dos gargalos da GPU é examinar uma captura da GPU de um profiler de GPU adequado. Qual ferramenta você usa depende do hardware alvo e da API gráfica escolhida.

arte chave unity 21 11
Quer saber mais?

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

Melhores práticas para perfilagem do desempenho do jogo | Unity