Como criar o perfil do seu jogo WebGL com a sinalização about:tracing

Lilian Thompson
Lilli Thompson

Se você não pode mensurar, não pode melhorá-lo.

Lord Kelvin

Para agilizar seus jogos HTML5, você precisa primeiro identificar os gargalos de desempenho, mas isso pode ser difícil. Avaliar os dados de quadros por segundo (QPS) é um começo, mas, para ter uma visão completa, você precisa entender as nuances das atividades do Chrome.

A ferramenta about:tracing fornece um insight que ajuda a evitar soluções alternativas precipitadas voltadas à melhoria do desempenho, mas que são essencialmente adivinhações bem-intencionadas. Você economizará muito tempo e energia, entenderá melhor o que o Chrome está fazendo em cada frame e usará essas informações para otimizar seu jogo.

Olá sobre:rastreamento

A ferramenta about:tracing do Chrome oferece uma janela para todas as atividades do Chrome durante um período com tanta granularidade que você pode achar difícil no começo. Muitas das funções do Chrome são instrumentadas para rastreamento pronto para uso. Portanto, sem qualquer instrumentação manual, você ainda pode usar o about:tracing para acompanhar seu desempenho. Consulte uma seção posterior sobre como instrumentar seu JS manualmente.

Para acessar a visualização de rastreamento, basta digitar "about:tracing" na omnibox (barra de endereço) do Chrome.

omnibox do Chrome
Digite "about:tracing" na omnibox do Chrome

Na ferramenta de rastreamento, você pode começar a gravar, executar o jogo por alguns segundos e conferir os dados de rastreamento. Este é um exemplo de como os dados podem ser:

Resultado de rastreamento simples
Resultado de rastreamento simples

Sim, é confuso mesmo. Vamos falar sobre como ler.

Cada linha representa um processo que está sendo criado, o eixo esquerdo-direito indica o tempo, e cada caixa colorida é uma chamada de função instrumentada. Há linhas para vários tipos diferentes de recursos. Os mais interessantes para criação de perfil de jogos são o CrGpuMain, que mostra o que a Unidade de Processamento Gráfico (GPU) está fazendo, e o CrRendererMain. Cada trace contém linhas CrRendererMain para cada guia aberta durante o período de rastreamento (incluindo a própria guia about:tracing).

Ao ler dados de trace, sua primeira tarefa é determinar qual linha do CrRendererMain corresponde ao seu jogo.

Resultado de rastreamento simples destacado
Resultado do rastro simples destacado

Neste exemplo, os dois candidatos são: 2216 e 6516. Atualmente, não há uma maneira refinada de escolher seu aplicativo, exceto procurar a linha que está fazendo muitas atualizações periódicas (ou, se você tiver instrumentado manualmente seu código com pontos de rastreamento, para procurar a linha que contém os dados de rastreamento). Neste exemplo, parece que o 6516 está executando um loop principal a partir da frequência de atualizações. Se você fechar todas as outras guias antes de iniciar o rastro, vai ficar mais fácil descobrir o CrRendererMain correto. No entanto, ainda pode haver linhas CrRendererMain para processos diferentes do seu jogo.

Como encontrar a moldura

Depois de localizar a linha correta na ferramenta de rastreamento do jogo, a próxima etapa é encontrar o loop principal. O loop principal parece um padrão repetido nos dados de rastreamento. Navegue pelos dados de rastreamento usando as teclas W, A, S, D: A e D para mover-se para a esquerda ou direita (para frente e para trás no tempo) e W e S para aumentar e diminuir o zoom nos dados. O esperado é que o loop principal seja um padrão que se repita a cada 16 milissegundos se o jogo estiver sendo executado a 60 Hz.

Parece que há três frames de execução
Aparecem três frames de execução

Depois de localizar os sinais de funcionamento do jogo, você pode investigar o que o código faz exatamente em cada frame. Use W, A, S, D para aumentar o zoom até conseguir ler o texto nas caixas de função.

Profundo de um frame de execução
Dentro de um frame de execução

Esta coleção de caixas mostra uma série de chamadas de função, com cada chamada representada por uma caixa colorida. Cada função foi chamada pela caixa acima. Por isso, neste caso, você pode ver que MessageLoop::RunTask chamado RenderWidget::OnSwapBuffersComplete, que, por sua vez, chamou RenderWidget::DoDeferredUpdate e assim por diante. Com a leitura desses dados, você terá uma visão completa do que chamou e quanto tempo cada execução levou.

Mas é aqui que a coisa fica um pouco aderente. As informações expostas pelo about:tracing são as chamadas de função brutas do código-fonte do Chrome. Você pode fazer suposições sobre o que cada função está fazendo a partir do nome, mas as informações não são exatamente fáceis de usar. É útil ver o fluxo geral do frame, mas você precisa de algo um pouco mais legível para realmente descobrir o que está acontecendo.

Adicionar tags de trace

Felizmente, há uma maneira fácil de adicionar instrumentação manual ao seu código para criar dados de rastreamento: console.time e console.timeEnd.

console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");

O código acima cria novas caixas no nome da visualização de rastreamento com as tags especificadas. Portanto, se você executar o app novamente, verá as caixas "update" e "render" que mostram o tempo decorrido entre as chamadas de início e término de cada tag.

Tags adicionadas manualmente
Tags adicionadas manualmente

Com isso, você pode criar dados de rastreamento legíveis para rastrear pontos de acesso em seu código.

GPU ou CPU?

Com gráficos acelerados por hardware, uma das perguntas mais importantes que você pode fazer durante a criação de perfil é: esse código é vinculado à GPU ou à CPU? Com cada frame, você fará um trabalho de renderização na GPU e alguma lógica na CPU. Para entender o que está deixando seu jogo lento, você precisa conferir como o trabalho está sendo equilibrado nos dois recursos.

Primeiro, encontre a linha na visualização de rastreamento chamada CrGPUMain, que indica se a GPU está ocupada em um momento específico.

Traces de GPU e CPU

Observe que cada frame do jogo faz com que a CPU funcione no CrRendererMain e na GPU. O rastro acima mostra um caso de uso muito simples em que a CPU e a GPU ficam inativas na maior parte do frame de 16 ms.

A visualização de rastreamento se torna muito útil quando você tem um jogo com execução lenta e não tem certeza de qual recurso está usando o máximo de recursos. Analisar como as linhas da GPU e da CPU se relacionam é essencial para a depuração. Use o mesmo exemplo de antes, mas inclua um pouco mais de trabalho na repetição de atualização.

console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");

console.time("render");
render();
console.timeEnd("render");

Agora você verá um rastro parecido com este:

Traces de GPU e CPU

O que esse rastro nos diz? O frame na imagem muda de 2.270 ms para 2.320 ms, o que significa que cada frame leva cerca de 50 ms (uma taxa de frame de 20 Hz). Você pode ver lascas de caixas coloridas que representam a função de renderização ao lado da caixa de atualização, mas o frame é totalmente dominado pela atualização.

Ao contrário do que acontece na CPU, é possível notar que a GPU ainda fica ociosa na maior parte de cada frame. Para otimizar esse código, você pode procurar operações que podem ser feitas no código do sombreador e movê-las para a GPU para aproveitar melhor os recursos.

E quando o código do sombreador é lento e a GPU está sobrecarregada? E se removermos o trabalho desnecessário da CPU e, em vez disso, adicionarmos trabalho ao código do sombreador de fragmento? Confira um sombreador de fragmento desnecessariamente caro:

#ifdef GL_ES
precision highp float;
#endif
void main(void) {
  for(int i=0; i<9999; i++) {
    gl_FragColor = vec4(1.0, 0, 0, 1.0);
  }
}

Como é um trace de código que usa esse sombreador?

Traces de GPU e CPU ao usar código GPU lento
Traces de GPU e CPU ao usar código GPU lento

Novamente, observe a duração de um frame. Aqui, o padrão de repetição varia de cerca de 2.750 ms a 2.950 ms, com uma duração de 200 ms (frame rate de cerca de 5 Hz). A linha CrRendererMain está quase completamente vazia, o que significa que a CPU fica ociosa na maior parte do tempo, enquanto a GPU está sobrecarregada. Isso é um sinal de que seus sombreadores são muito pesados.

Se você não souber exatamente o que estava causando a baixa frame rate, poderá observar a atualização de 5 Hz e ficar tentado a acessar o código do jogo e começar a tentar otimizar ou remover a lógica dele. Nesse caso, isso não seria nada bom, porque a lógica do loop do jogo não está consumindo tempo. Na verdade, o que esse rastro indica é que fazer mais trabalho da CPU em cada frame seria essencialmente "livre", porque a CPU fica ociosa, então dar mais trabalho não vai afetar a duração do frame.

Exemplos reais

Agora vamos ver como são os dados de rastreamento de um jogo real. Uma das coisas mais legais sobre jogos criados com tecnologias de web aberta é que você pode ver o que está acontecendo em seus produtos favoritos. Se quiser testar ferramentas de criação de perfil, você pode escolher seu título WebGL favorito na Chrome Web Store e criar um perfil com sobre:rastreamento. Esse é um rastro de exemplo retirado do excelente jogo WebGL Skid Racer.

Como rastrear um jogo real
Como rastrear um jogo real

Cada frame leva cerca de 20 ms, ou seja, uma frame rate é de cerca de 50 QPS. É possível notar que o trabalho é equilibrado entre a CPU e a GPU, mas a GPU é o recurso com maior demanda. Se você quiser ver como é criar o perfil de exemplos reais de jogos WebGL, experimente alguns dos títulos da Chrome Web Store criados com WebGL, incluindo:

Conclusão

Se você quiser que seu jogo seja executado a 60 Hz, para cada frame todas as operações precisam caber em 16 ms de CPU e 16 ms de tempo de GPU. Você tem dois recursos que podem ser usados em paralelo e pode alternar o trabalho entre eles para maximizar o desempenho. A visualização about:tracing do Chrome é uma ferramenta inestimável para entender o que seu código está realmente fazendo e ajuda você a maximizar seu tempo de desenvolvimento lidando com os problemas certos.

A seguir

Além da GPU, você também pode rastrear outras partes do tempo de execução do Chrome. O Chrome Canary, a versão em estágio inicial do Chrome, está instrumentado para rastrear E/S, IndexedDB e várias outras atividades. Leia este artigo do Chromium para entender melhor o estado atual dos eventos de rastreamento.

Se você é um desenvolvedor de jogos da Web, assista o vídeo abaixo. É uma apresentação da equipe de Mediador de Desenvolvedores de Jogos do Google na GDC 2012 sobre otimização de desempenho para jogos do Chrome: