Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Otimização da CPU
Medindo o desempenho
Temos que saber onde estão os "gargalos" para saber como acelerar o nosso programa. Os gargalos são as partes mais lentas do programa que limitam o ritmo com que todo o resto pode progredir. Concentrar-se nos gargalos nos permite focar nossos esforços na otimização das áreas que nos darão a maior melhoria de velocidade, em vez de gastar muito tempo otimizando funções que levarão a pequenas melhorias de desempenho.
Para a CPU, a maneira mais fácil de identificar gargalos é usar um profiler.
Perfiladores de CPU
Os profilers rodam junto com o seu programa e fazem medições de tempo para descobrir qual proporção de tempo é gasta em cada função.
A IDE do Godot convenientemente possui um profiler embutido. Ele não roda toda vez que você inicia seu projeto: ele deve ser iniciado e parado manualmente. Isso ocorre porque, como a maioria dos profilers, registrar essas medições de tempo pode deixar seu projeto significativamente mais lento.
Depois de fazer o profiling, você pode analisar os resultados de um quadro.
Resultados de um profiling de um dos projetos de demonstração.
Nota
Podemos ver o custo de processos internos, como física e áudio, bem como o custo de nossas próprias funções de script na parte inferior.
O tempo gasto esperando por vários servidores internos pode não ser contabilizado nos profilers. Este é um bug conhecido.
Quando um projeto está rodando lentamente, você frequentemente verá uma função ou processo óbvio consumindo muito mais tempo do que os outros. Este é o seu gargalo principal, e você geralmente pode aumentar a velocidade otimizando essa área.
Para mais informações sobre como usar o analisador de perfis embutido do Godot, veja Painel do depurador.
Perfiladores externos
Embora o profiler da IDE do Godot seja muito conveniente e útil, às vezes você precisa de mais poder e da capacidade de fazer o profiling do próprio código-fonte da engine do Godot.
Você pode usar uma série de profilers C++ de terceiros para fazer isso.
Exemplo de resultados do Callgrind, que faz parte do Valgrind.
A partir da esquerda, o Callgrind lista a porcentagem de tempo dentro de uma função e de suas funções filhas (Inclusive), a porcentagem de tempo gasta dentro da própria função, excluindo as funções filhas (Self), o número de vezes que a função é chamada, o nome da função e o arquivo ou módulo.
Neste exemplo, podemos ver que quase todo o tempo é gasto na função Main::iteration(). Esta é a função mestre no código-fonte do Godot que é chamada repetidamente. Ela faz com que os quadros sejam desenhados, os ticks de física sejam simulados e os nós e scripts sejam atualizados. Uma grande proporção do tempo é gasta nas funções para renderizar uma canvas (66%), porque este exemplo usa um benchmark 2D. Abaixo disso, vemos que quase 50% do tempo é gasto fora do código do Godot em libglapi e i965_dri (o driver gráfico). Isso nos diz que uma grande proporção do tempo de CPU está sendo gasta no driver gráfico.
Este é na verdade um excelente exemplo porque, em um mundo ideal, apenas uma proporção muito pequena de tempo seria gasta no driver gráfico. Isso é um indício de que há um problema com excesso de comunicação e trabalho sendo feito na API gráfica. Esse profiling específico levou ao desenvolvimento do batching 2D, que acelera muito a renderização 2D ao reduzir os gargalos nessa área.
Funções de temporização manual
Outra técnica útil, especialmente depois de identificar o gargalo usando um profiler, é cronometrar manualmente a função ou área sob teste. Os detalhes variam dependendo da linguagem, mas em GDScript, você faria o seguinte:
var time_start = Time.get_ticks_usec()
# Your function you want to time
update_enemies()
var time_end = Time.get_ticks_usec()
print("update_enemies() took %d microseconds" % (time_end - time_start))
var timeStart = Time.GetTicksUsec();
// Your function you want to time.
UpdateEnemies();
var timeEnd = Time.GetTicksUsec();
GD.Print($"UpdateEnemies() took {timeEnd - timeStart} microseconds");
Ao cronometrar funções manualmente, geralmente é uma boa ideia executar a função muitas vezes (1.000 ou mais vezes), em vez de apenas uma (a menos que seja uma função muito lenta). O motivo para fazer isso é que os cronômetros costumam ter precisão limitada. Além disso, as CPUs agendam processos de maneira aleatória. Portanto, uma média sobre uma série de execuções é mais precisa do que uma única medição.
À medida que você tenta otimizar as funções, certifique-se de fazer o profiling repetidamente ou cronometrá-las à medida que avança. Isso lhe dará um feedback crucial sobre se a otimização está funcionando (o não).
Caches
Os caches de CPU são algo que deve ser particularmente atento, especialmente ao comparar resultados de temporização de duas versões diferentes de uma função. Os resultados podem ser altamente dependentes se os dados estão no cache da CPU ou não. As CPUs não carregam dados diretamente da RAM do sistema, embora seja enorme em comparação com o cache da CPU (vários gigabytes em vez de alguns megabytes). Isso ocorre porque o acesso à RAM do sistema é muito lento. Em vez disso, as CPUs carregam dados de um banco de memória menor e mais rápido chamado cache. Carregar dados do cache é muito rápido, mas toda vez que você tenta carregar um endereço de memória que não está armazenado no cache, o cache deve fazer uma viagem para a memória principal e carregar lentamente alguns dados. Esse atraso pode fazer com que a CPU fique ociosa por um longo tempo e é conhecido como "falha de cache".
Isso significa que a primeira vez que você executa uma função, ela pode rodar lentamente porque os dados não estão no cache da CPU. Na segunda vez e nas posteriores, ela pode rodar muito mais rápido porque os dados estão no cache. Devido a isso, sempre use médias ao cronometrar e fique atento aos efeitos do cache.
Understanding caching is also crucial to CPU optimization. If you have an algorithm (routine) that loads small bits of data from randomly spread out areas of main memory, this can result in a lot of cache misses, a lot of the time, the CPU will be waiting around for data instead of doing any work. Instead, if you can make your data accesses localized, or even better, access memory in a linear fashion (like a continuous list), then the cache, and therefore the CPU, will be able to work as efficiently as possible.
O Godot geralmente cuida desses detalhes de baixo nível para você. Por exemplo, as Server APIs garantem que os dados já estejam otimizados para caching em coisas como renderização e física. Ainda assim, você deve ficar especialmente atento ao caching ao escrever GDExtensions.
Idiomas
O Godot suporta uma variedade de linguagens diferentes, e vale a pena ter em mente que existem compensações envolvidas. Algumas linguagens são projetadas para facilidade de uso à custa da velocidade, e outras são mais rápidas, mas é mais difícil de se trabalhar.
As funções internas da engine rodam na mesma velocidade, independentemente da linguagem de script que você escolher. Se o seu projeto estiver fazendo muitos cálculos em seu próprio código, considere mover esses cálculos para uma linguagem mais rápida.
GDScript
GDScript foi projetado para ser fácil de usar e iterar, sendo ideal para criar muitos tipos de jogos. No entanto, nesta linguagem, a facilidade de uso é considerada mais importante do que o desempenho. Se você precisar fazer cálculos pesados, considere mover parte do seu projeto para uma das outras linguagens.
C#
C# é popular e tem suporte de primeira classe no Godot. Ele oferece um bom equilíbrio entre velocidade e facilidade de uso. Cuidado com possíveis pausas e vazamentos do coletor de lixo (garbage collection) que podem ocorrer durante a gameplay, no entanto. Uma abordagem comum para contornar problemas com o garbage collection é usar object pooling, o que está fora do escopo deste guia.
Outras línguas
Terceiros fornecem suporte para várias outras linguagens, incluindo Rust.
C++
O Godot é escrito em C++. Usar C++ geralmente resultará no código mais rápido. No entanto, em um nível prático, é a linguagem mais difícil de implantar nas máquinas dos usuários finais em diferentes plataformas. As opções para usar C++ incluem GDExtensions e módulos customizados.
Partes_Paralelizáveis
Considere o uso de partes_paralelizáveis(threads) ao fazer muitos cálculos que podem ser executados paralelamente entre si. As CPUs modernas têm vários núcleos, cada um capaz de realizar uma quantidade limitada de trabalho. Ao distribuir o trabalho por várias partes_paralelizáveis, você pode avançar ainda mais em direção ao pico de eficiência da CPU.
A desvantagem do uso de partes_paralelizáveis (threads) é que você precisa ser extremamente cuidadoso. Como cada núcleo da CPU opera de forma independente, eles podem acabar tentando acessar a mesma memória ao mesmo tempo. Uma parte_paralelizável pode ler uma variável enquanto outro escreve: isso é chamado de condição de corrida. Antes de usar threads, certifique-se de compreender os perigos e como tentar evitar essas condições de corrida. Partes_Paralelizáveis(threads) podem tornar a depuração consideravelmente mais difícil.
Para mais informações sobre threads, veja Usando múltiplas partes_paralelizáveis(threads).
SceneTree
Embora os Nodes sejam um conceito incrivelmente poderoso e versátil, esteja ciente de que cada nó tem um custo. Funções internas como _process() e _physics_process() se propagam pela árvore. Esse gerenciamento interno pode reduzir o desempenho quando você tem um número muito grande de nós (quantos exatamente depende da plataforma de destino e pode variar de milhares a dezenas de milhares, portanto, certifique-se de fazer o profiling do desempenho em todas as plataformas de destino durante o desenvolvimento).
Cada nó é tratado individualmente no renderizador do Godot. Portanto, um número menor de nós com mais conteúdo em cada um pode levar a um melhor desempenho.
Uma peculiaridade da SceneTree é que às vezes você pode obter um desempenho muito melhor removendo nós da SceneTree, em vez de pausá-los ou ocultá-los. Você não precisa deletar um nó destacado. Você pode, por exemplo, manter uma referência a um nó, destacá-lo da scene tree usando Node.remove_child(node), e depois anexá-lo novamente mais tarde usando Node.add_child(node). Isso pode ser muito útil para adicionar e remover áreas de um jogo, por exemplo.
Você pode evitar a SceneTree completamente usando as Server APIs. Para mais informações, veja Otimização usando Servers.
Física
Em algumas situações, a física pode acabar se tornando um gargalo. Este é particularmente o caso de mundos complexos e grandes quantidades de objetos de física.
Aqui estão algumas técnicas para acelerar a física:
Tente usar versões simplificadas da sua geometria renderizada para os formatos de colisão (collision shapes). Muitas vezes, isso não será perceptível para os usuários finais, mas pode aumentar muito o desempenho.
Tente remover objetos da física quando eles estiverem fora de vista / fora da área atual, ou reutilizar objetos de física (talvez você permita 8 monstros por área, por exemplo, e os reutilize).
Outro aspecto crucial para a física é a taxa de ticks de física (physics tick rate). em alguns jogos, você pode reduzir drasticamente a taxa de ticks e, em vez de, por exemplo, atualizar a física 60 vezes por segundo, você pode atualizá-la apenas 30 ou até 20 vezes por segundo. Isso pode reduzir bastante a carga da CPU.
O lado negativo de alterar a taxa de ticks de física é que você pode obter movimentos travados ou trepidação (jitter) quando a taxa de atualização da física não corresponde aos quadros por segundo renderizados. Além disso, diminuir a taxa de ticks de física aumentará o atraso de entrada (input lag). Recomenda-se manter a taxa de ticks de física padrão (60 Hz) na maioria dos jogos que apresentam movimento do jogador em tempo real.
A solução para a trepidação é usar interpolação de passo de tempo fixo (fixed timestep interpolation), que envolve suavizar as posições e rotações renderizadas ao longo de múltiplos quadros para corresponder à física. O Godot possui interpolação física integrada, sobre a qual você pode ler aqui. Em termos de desempenho, a interpolação é uma operação muito barata em comparação com a execução de um tick de física. Ela é ordens de magnitude mais rápida, portanto, isso pode ser um ganho de desempenho significativo, ao mesmo tempo em que reduz a trepidação.