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

Lilli Thompson
Lilli Thompson

Se você não puder medir, não poderá melhorá-lo.

Lord Kelvin (link em inglês)

Para que seus jogos HTML5 sejam executados mais rapidamente, primeiro você precisa 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 geral, você precisa entender as nuances das atividades do Chrome.

A ferramenta about:tracing fornece o insight que ajuda a evitar soluções precipitadas voltadas para a melhoria do desempenho, mas que são essencialmente adivinhações bem intencionadas. Você economizará muito tempo e energia, obterá uma imagem mais clara do que o Google Chrome está fazendo com cada quadro e usará essas informações para otimizar seu jogo.

Olá sobre:rastreamento

A ferramenta about:tracing do Chrome mostra uma janela de todas as atividades do Chrome durante um período com tanta granularidade que pode parecer exagerada no início. Muitas das funções do Chrome são instrumentadas para rastreamento imediato. Dessa forma, sem a instrumentação manual, você ainda pode usar about:tracing para acompanhar o desempenho. Consulte uma seção posterior sobre como instrumentar manualmente seu JS

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

Com a 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

É, isso é confuso. Vamos aprender a ler.

Cada linha representa um processo com o perfil 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 criar perfis 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 rastreamento, sua primeira tarefa é determinar qual linha CrRendererMain corresponde ao seu jogo.

Resultado de rastreamento simples destacado
Resultado de rastreamento simples destacado

Neste exemplo, os dois candidatos são: 2216 e 6516. Infelizmente, 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 seus dados de rastreamento). Neste exemplo, parece que 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, será mais fácil encontrar o CrRendererMain correto. No entanto, ainda pode haver linhas CrRendererMain para outros processos além do seu jogo.

Como encontrar seu frame

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 de repetição nos dados de rastreamento. É possível navegar pelos dados de rastreamento usando as teclas W, A, S e D: A e D para mover 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. Você espera que o loop principal seja um padrão que se repete a cada 16 milissegundos se o jogo estiver sendo executado a 60 Hz.

Parece três frames de execução.
Parecem três frames de execução

Depois de localizar o batimento cardíaco do jogo, você pode investigar o que exatamente seu código está fazendo em cada frame. Use W, A, S, D para aumentar o zoom até que possa ler o texto nas caixas de função.

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

Esse conjunto de caixas mostra uma série de chamadas de função, em que cada uma é representada por uma caixa colorida. Cada função era chamada pela caixa acima dela, então, neste caso, você pode ver que MessageLoop::RunTask chamado RenderWidget::OnSwapBuffersComplete, que por sua vez chamou RenderWidget::DoDeferredUpdate e assim por diante. Ao ler esses dados, você pode ter uma visão completa do que chamou e quanto tempo cada execução levou.

Mas aqui é um pouco pegajoso. As informações expostas por about:tracing são chamadas de funções brutas do código-fonte do Chrome. Você pode fazer suposições sobre o que cada função faz 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.

Como adicionar tags de rastreamento

Felizmente, há uma maneira fácil de adicionar instrumentação manual ao seu código para criar dados de trace: 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á "update" e "renderizar" caixas que mostram o tempo decorrido entre as chamadas inicial e final de cada tag.

Tags adicionadas manualmente
Tags adicionadas manualmente

Assim, é possível criar dados de rastreamento legíveis por humanos para rastrear pontos de acesso no 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? Em 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 mais lento, é preciso conferir como o trabalho está equilibrado entre os 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

É possível ver que cada frame do jogo causa o trabalho da CPU 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 de cada frame de 16 ms.

A visualização de rastreamento é muito útil quando você tem um jogo que está sendo executado lentamente e não tem certeza de qual recurso está maximizando. Analisar a relação entre as linhas GPU e CPU é a chave da depuração. Use o mesmo exemplo de antes, mas adicione um pouco mais de trabalho ao loop 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ê vai encontrar um rastro como este:

Traces de GPU e CPU

O que esse rastro nos diz? O frame mostrado vai de cerca de 2.270 ms para 2.320 ms, o que significa que cada frame leva cerca de 50 ms (uma taxa de frames de 20 Hz). É possível ver slives de caixas coloridas representando a função de renderização ao lado da caixa de atualização, mas o frame é totalmente dominado pela própria atualização.

Em contraste com o que está acontecendo com a CPU, é possível ver que a GPU ainda fica inativa na maior parte de cada frame. Para otimizar esse código, você pode procurar operações que possam ser realizadas no código do shader e movê-las para a GPU a fim de aproveitar melhor os recursos.

E quando o código do shader estiver lento e a GPU estiver sobrecarregada? E se removermos o trabalho desnecessário da CPU e adicionarmos trabalho ao código do shader de fragmento? Confira um shader 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 rastro de código que usa esse sombreador?

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

Mais uma vez, observe a duração de um frame. Aqui, o padrão de repetição vai de cerca de 2.750 ms para 2.950 ms, uma duração de 200 ms (taxa de frames de cerca de 5 Hz). A linha CrRendererMain está quase totalmente vazia, o que significa que a CPU fica ociosa na maior parte do tempo, enquanto a GPU está sobrecarregada. Esse é um sinal claro de que os sombreadores são muito pesados.

Se você não tinha visibilidade exata do que estava causando a baixa taxa de quadros, poderia observar a atualização de 5 Hz e ficaria tentado a acessar o código do jogo e começar a tentar otimizar ou remover a lógica do jogo. Nesse caso, isso não seria nada bom, porque a lógica do loop de jogo não é o que está consumindo tempo. Na verdade, o que esse trace indica é que, ao fazer mais trabalho da CPU, cada frame seria basicamente "sem custo financeiro". em que a CPU fica ociosa. Portanto, dar mais trabalho a ela não afetará o tempo que o frame leva.

Exemplos reais

Agora, vamos conferir como são os dados de rastreamento de um jogo real. Uma das coisas mais legais sobre jogos criados com tecnologias da Web aberta é que você pode ver o que está acontecendo nos 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 about:tracing. Este é um exemplo de rastro retirado do excelente jogo WebGL Skid Racer.

Traçar um jogo real
Rastrear um jogo real

Parece que cada frame leva cerca de 20 ms, ou seja, o frame rate tem cerca de 50 QPS. O trabalho está equilibrado entre CPU e GPU, mas a GPU é o recurso mais procurado. Se você quiser ver como é criar perfis com exemplos reais de jogos WebGL, experimente alguns dos títulos da Chrome Web Store criados com WebGL, incluindo:

Conclusão

Se você quer 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 receber insights sobre o que o código realmente faz. Além disso, ela ajuda você a maximizar o 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 inicial do Chrome, está instrumentado para rastrear IO, IndexedDB e várias outras atividades. Leia este artigo do Chromium para entender melhor o estado atual dos eventos de rastreamento.

Se você desenvolve jogos para a Web, confira o vídeo abaixo. É uma apresentação da equipe de mediadores de desenvolvedores de jogos do Google na GDC 2012 sobre a otimização de desempenho para jogos do Chrome: