Dicas de otimização de CPU e GPU

Este documento mostra como otimizar a performance do jogo usando ferramentas para identificar e resolver gargalos de CPU e GPU.

Otimização da CPU

Se a análise mostrar que o jogo está limitado pela CPU, é essencial investigar mais a fundo. Para isso, é necessário identificar as linhas de execução ou APIs específicas que causam gargalos e reduzem a taxa de FPS.

Para otimização da CPU, uma solução universal geralmente não é eficaz. Em vez disso, identifique a carga de trabalho mais exigente com base no jogo ou na cena e otimize a lógica e as funções relevantes.

Ferramentas de rastreamento de tempo do mecanismo de jogo

As ferramentas a seguir podem ajudar nessa análise:

Insights incríveis

Nos projetos do Unreal Engine, a ferramenta Unreal Insight facilita a análise das informações de rastreamento de tempo para threads individuais que compõem um frame.

Por exemplo, a GameThread geralmente usa a maior proporção de tempo da CPU, principalmente devido ao tempo de tick. Além disso, uma parte substancial do tempo de marcação é consumida por tarefas associadas a FActorComponentTickFunction.

Para otimizar FActorComponentTick, é fundamental excluir cálculos e implementar a eliminação de caracteres e objetos posicionados fora do campo de visão da câmera. Além disso, usar animações baseadas no LOD (nível de detalhe) pode gerar mais melhorias de performance.

Linha do tempo de rastreamento do Unreal Insight mostrando os tempos de execução de GameThread, RenderThread e RHIThread
Rastreamento de insights do Unreal com GameThread, RenderThread e RHIThread (clique para ampliar).

Unity Profiler (Unity)

A análise usando o Unity Profiler revela que a linha de execução principal consome mais de 45 ms, com PostLateUpdate.FinishFrameRendering ocupando 16,23 ms, o que a torna a operação mais demorada. Dentro disso, várias invocações de Inl_RenderCameraStack são observadas. É recomendável verificar a necessidade das câmeras ativadas e otimizá-las de acordo com isso.

Linha do tempo do Unity Profiler mostrando a linha de execução principal aguardando Gfx.WaitForPresentOnGfxThread
Exemplo de vinculação de GPU para o Unity Profiler (clique para ampliar).

Ferramentas de criação de perfis no nível do sistema

Use as seguintes ferramentas de criação de perfil:

Perfetto

Com o rastreamento do Perfetto, é possível determinar as atribuições de núcleo da CPU e os detalhes de execução de cada linha de execução em um dispositivo Android. Isso permite identificar gargalos de desempenho analisando dados de execução de linhas de execução.

Caso de sobrecarga da CPU

O rastreamento indica que a carga de trabalho nas GameThread e RenderThread está causando atrasos no QueuePresent da RHI Thread, levando a um cenário vinculado à CPU, com base no VSync.

Rastreamento do Perfetto mostrando os tempos de execução de GameThread, RenderThread e RHIThread
Traces do Perfetto com detalhes da execução da CPU (clique para ampliar).

Caso de sobrecarga da GPU

O rastreamento indica que a conclusão da GPU excede 25 ms, o que significa um cenário vinculado à GPU.

Rastreamento do Perfetto mostrando o bloco de conclusão da GPU aguardando a conclusão da GPU
Rastreamentos do Perfetto com detalhes do overhead da GPU (clique para ampliar).

Simpleperf

Para identificar as funções com o maior uso atual de CPU, use o simpleperf. Para ter os melhores resultados, recomendamos classificar essas funções para priorizar e resolver primeiro aquelas com maior uso.

Saída do Simpleperf mostrando funções com o maior uso da CPU
Criação de perfil de CPU do Simpleperf: análise da hierarquia de chamadas de função e da utilização de recursos (clique para ampliar).

O Simpleperf ajuda a analisar dados sobre funções que usam mais tempo de CPU. Para otimizar o uso da CPU, comece com as funções que usam mais CPU. Neste exemplo, USkeletalMeshComponent, associado à animação em ActorComponentTickFunctions, usa mais CPU.

Otimização de GPU

Se a análise mostrar que o jogo é vinculado à GPU, será essencial fazer uma investigação mais detalhada. Isso exige o uso de várias ferramentas e técnicas para otimização e análise de GPU.

Para otimizar a GPU, use um depurador de frames para analisar o pipeline de renderização e as chamadas de desenho de cada cena. Além disso, é necessário entender bem a arquitetura da GPU e o comportamento do pipeline para identificar operações desnecessárias ou áreas a serem otimizadas.

As seções a seguir explicam métodos e ferramentas para otimização de GPU.

Eliminar RenderPasses desnecessários

Para melhorar o desempenho da renderização e reduzir a carga de trabalho da GPU, elimine as transmissões de renderização desnecessárias. Isso inclui qualquer transmissão de renderização que não tenha chamadas de desenho ou cuja saída não seja usada no frame final.

Use um depurador de GPU, como o RenderDoc, para analisar o pipeline de renderização e identificar oportunidades de otimização.

  1. Nenhuma chamada de desenho:verifique se a transmissão de renderização inclui alguma chamada de desenho. Se não houver chamadas de desenho, remova a transmissão.

  2. Saída não usada:verifique se as transmissões subsequentes acessam ou mostram as saídas de transmissão de renderização, por exemplo, cor ou profundidade. Se não estiverem, remova o passe.

  3. Cartões mescláveis:identifique os cartões que podem ser mesclados:

    • Mesmo framebuffer ou anexos
    • Operações de carga ou armazenamento compatíveis
    • Sem barreiras de dependência entre eles
Navegador de eventos do RenderDoc mostrando transmissões de renderização e chamadas de desenho do Vulkan
RenderPass e sequência de comandos da GPU no RenderDoc (clique para ampliar).

Minimizar operações de carga ou armazenamento

As operações de carga ou armazenamento consomem muitos recursos porque usam muita memória. Minimize operações de carga e armazenamento desnecessárias. Realize essas ações apenas quando anexos em um RenderPass forem necessários. Caso contrário, substitua-as por operações Clear ou Don't care para reduzir o overhead.

Como otimizar

Use um depurador de GPU, como o RenderDoc, para analisar o pipeline de renderização e identificar as seguintes oportunidades de otimização:

  1. Carregar:se um anexo de transmissão de renderização não usar dados de uma transmissão ou anexo anterior, uma operação de carregamento será desnecessária. Nesses casos, usar Don't care ou Clear pode reduzir a sobrecarga.

  2. Armazenamento:se um anexo de transmissão de renderização não for usado após a transmissão atual, a operação de armazenamento será desnecessária. Nesses casos, use Don't care ou Clear.

  3. Substituir:determine se as configurações atuais de carga ou armazenamento podem ser substituídas por Clear ou Don't Care sem afetar o frame final.

Navegador de eventos e inspetor de recursos do RenderDoc analisando o layout da imagem e as transmissões de renderização
Análise do pipeline de renderização do RenderDoc (clique para ampliar).

Evite o descarte para ativar o Early-Z

O Early-Z melhora o desempenho em plataformas móveis. No entanto, uma instrução discard em um shader desativa automaticamente o Early-Z. Se a instrução discard não for essencial, remova-a.

Aceleração inicial no eixo Z

Essa otimização reduz significativamente as operações do sombreador de fragmentos e melhora o desempenho da GPU.

Early-Z Testes de profundidade e estêncil

Tabela comparando métricas de desempenho de CPU e GPU quando o Early-Z está ativado ou desativado
Impacto na performance da aceleração Early-Z (clique para ampliar).

Como otimizar

Use um depurador de GPU, como o RenderDoc, para analisar o pipeline de renderização e identificar as seguintes oportunidades de otimização:

  1. Uso de discard em shaders de fragmentos:a palavra-chave discard impede que a GPU realize testes de profundidade antecipados porque a visibilidade do fragmento não é conhecida com antecedência.

  2. Modificação de gl_FragDepth:a modificação dinâmica de gl_FragDepth muda a profundidade de um fragmento, o que desativa a otimização Early-Z porque a profundidade final é desconhecida antes do processamento do fragmento.

  3. Alfa para cobertura ativada:quando a opção alfa para cobertura está ativada (geralmente usada na renderização MSAA), a cobertura de fragmentos depende dos valores alfa. Isso pode atrasar o teste de profundidade e desativar o Early-Z.

Comparação de fragmentos por pixel com e sem a palavra-chave do shader de descarte
Depurador de GPU RenderDoc para análise (clique para ampliar).

Otimizar formato de textura

A seleção ideal do formato de textura reduz o consumo de memória, aumenta a eficiência da largura de banda e melhora o desempenho da renderização. Usar formatos de alta precisão pode desperdiçar recursos da GPU sem oferecer vantagens visuais.

Como otimizar

Use um depurador de GPU, como o RenderDoc, para analisar o pipeline de renderização e identificar as seguintes oportunidades de otimização:

  1. Use D24S8 em vez de D32S8 para buffers de profundidade-stencil:usar D24S8 para buffers de profundidade-stencil reduz o consumo de memória em 20% em comparação com D32S8, com pouca ou nenhuma diferença perceptível na qualidade visual na maioria dos aplicativos.
  2. Use a compactação ASTC para texturas de cores:a compactação ASTC reduz significativamente o uso da memória de textura, em até 8 vezes em comparação com formatos não compactados, preservando a alta qualidade visual.
  3. Use formatos de ponto flutuante de meia precisão em vez de ponto flutuante de precisão total:use R16F ou RG16F para reduzir o consumo de largura de banda e armazenamento da memória. Esses formatos são adequados para buffers de pós-processamento.

Otimizar a complexidade da geometria

Minimizar a complexidade geométrica melhora o desempenho da renderização, principalmente em dispositivos móveis com recursos de GPU limitados. Isso envolve usar um número reduzido de vértices e triângulos, consolidar objetos para diminuir as chamadas de desenho e eliminar geometria não renderizada ou desnecessária. Técnicas como simplificação de malha, nível de detalhe (LOD) e eliminação de frustum ou oclusão podem reduzir significativamente a carga de trabalho da GPU e aumentar as taxas de frames.

Como otimizar

Use ferramentas de criação de perfil e depuradores de GPU, como RenderDoc, Android GPU Inspector ou outros analisadores de performance, para identificar gargalos de performance relacionados à geometria.

  1. Reduzir a contagem de triângulos:minimize o uso de polígonos, principalmente para objetos pequenos ou distantes.

  2. Use o nível de detalhamento (LOD): com base na distância da câmera, malhas mais simples são usadas automaticamente.

  3. Mesclar malhas pequenas:consolide objetos estáticos para reduzir as chamadas de desenho e o overhead da CPU.

  4. Frustum e remoção por oclusão:evite renderizar objetos que estão fora da visualização ou são obscurecidos por outros elementos.

Remover anexos desnecessários

Os anexos de transmissão de renderização (por exemplo, cor, profundidade, estêncil) consomem largura de banda de memória e recursos da GPU, mesmo que não sejam usados. Remover anexos desnecessários ou redundantes melhora o desempenho e reduz o consumo de energia, principalmente em plataformas móveis.

Como otimizar

Use ferramentas de criação de perfil e depuradores de GPU, como RenderDoc, Android GPU Inspector ou outros analisadores de desempenho, para identificar gargalos de desempenho relacionados à geometria.

  1. Verifique o uso real:há chamadas de desenho ou shaders que estão gravando ou lendo do anexo?
  2. Analisar a saída do frame:use RenderDoc ou utilitários comparáveis para determinar se o anexo contribui para a imagem final.
  3. Considere anexos temporários ou fictícios:anexos temporários ou uma operação de armazenamento "Não importa" devem ser usados para dados temporários que não exigem armazenamento permanente.

Otimizar a precisão do shader

Usar precisão excessivamente alta (por exemplo, highp em vez de mediump ou lowp) em shaders aumenta a carga de trabalho da GPU, o consumo de energia e a pressão de registro, principalmente em GPUs móveis. Ao usar a menor precisão adequada para variáveis (por exemplo, posições, cores, UVs), é possível melhorar o desempenho sem um impacto visual perceptível.

Tabela comparando métricas de desempenho da CPU e da GPU ao usar precisão de shader mediump versus highp
Impacto da precisão do shader na performance (clique para ampliar).

Como otimizar

Use ferramentas de criação de perfil e depuradores de GPU, como RenderDoc, Android GPU Inspector ou outros analisadores de desempenho, para identificar gargalos de desempenho relacionados à geometria.

  1. Revise o código do shader:avalie as variáveis do shader e confirme se a alta precisão é usada apenas quando necessário, como para cálculos de profundidade ou espaço na tela. Use precisão média ou baixa para cores, coordenadas UV ou valores que não exigem alta precisão.

  2. Use depuradores de GPU:utilitários de diagnóstico, como RenderDoc ou criadores de perfil de GPU móvel (por exemplo, AGI, Mali/GPU Inspector), identificam o uso elevado de registros ou interrupções de shader associadas a problemas de precisão.

Profiler de uso variável do Mali mostrando interpolação de 16 bits ao lado do código do shader usando mediump
Exemplo de ferramentas de criação de perfil e depuradores de GPU (clique para ampliar).

Ativar a eliminação de faces traseiras

A renderização de triângulos que ficam de costas para a câmera (faces traseiras) geralmente é desnecessária para objetos sólidos.

Como otimizar

O uso de VK_CULL_MODE_NONE pode afetar negativamente o desempenho porque força a GPU a renderizar as faces frontal e traseira, o que aumenta a carga de trabalho de renderização.

Registro de comando do Vulkan mostrando vkCmdSetCullMode definido como VK_CULL_MODE_NONE
Registros de depuração com remoção de faces traseiras (clique para ampliar).

Minimizar o overdraw em cenas da interface

Elimine chamadas de desenho e transmissões de renderização desnecessárias, principalmente em cenas de interface, para melhorar o desempenho da renderização e reduzir a carga de trabalho da GPU. Por exemplo, em uma cena de IU em que o mundo inteiro é renderizado antes de sobrepor a IU na tela, a renderização do mundo se torna redundante.

Como otimizar

Use um depurador de GPU, como o RenderDoc, para analisar o pipeline de renderização e identificar as seguintes oportunidades de otimização:

  1. Verifique a ausência de overdraw supérfluo. Em contextos de interface do usuário, em que toda a tela pode ser renderizada, confirme se as transmissões de renderização anteriores não estão sendo desenhadas em excesso desnecessariamente.
  2. Ative o teste de profundidade e a eliminação para otimizar a performance.
  3. Considere a ordem de renderização da frente para trás.
Navegador de eventos e visualizador de texturas do RenderDoc identificando uma transmissão de renderização de overdraw desnecessária
Exemplo para eliminar chamadas de desenho e transmissões de renderização desnecessárias (clique para ampliar).