Vamos falar um pouco sobre como observar quantos recursos de CPU o código JavaScript do aplicativo consome. Ao mesmo tempo, proponho construir nossa conversa em torno dos componentes - os componentes básicos do aplicativo. Com essa abordagem, qualquer esforço para melhorar a produtividade (ou esforços para encontrar as causas da desaceleração do programa) pode ser focado (espero) em pequenos fragmentos auto-suficientes do projeto. Ao mesmo tempo, presumo que seu aplicativo front-end, como muitos outros projetos modernos, foi criado reunindo pequenos fragmentos da interface adequados para uso repetido. Se não for assim, nosso raciocínio poderá ser aplicado a outro aplicativo, mas você terá que encontrar sua própria maneira de dividir seu código em grande escala em fragmentos e precisará pensar em como analisá-los.

Por que isso é necessário?
Por que medir o consumo de CPU por JavaScript? O fato é que, atualmente, o desempenho do aplicativo está mais frequentemente vinculado aos recursos do processador. Permitam-me citar livremente as palavras de Steve Soders e Pat Minan de uma entrevista que fiz para o
Planet Performance Podcast . Ambos disseram que o desempenho do aplicativo não se limita mais aos recursos ou latências da rede. As redes estão ficando cada vez mais rápidas. Os desenvolvedores, além disso, aprenderam a compactar as respostas de texto do servidor usando GZIP (ou melhor, usando brotli) e descobriram como otimizar as imagens. Tudo é muito simples.
O gargalo de desempenho dos aplicativos modernos são os processadores. Isto é especialmente verdade no ambiente móvel. E, ao mesmo tempo, aumentamos nossas expectativas sobre os recursos interativos dos aplicativos Web modernos. Esperamos que as interfaces de tais aplicativos funcionem muito rapidamente e sem problemas. E tudo isso requer mais e mais código JavaScript. Além disso, precisamos lembrar que 1 MB de imagens não é o mesmo que 1 MB de JavaScript. As imagens são baixadas progressivamente e, no momento, o aplicativo resolve outros problemas. Mas o código JavaScript geralmente é um recurso desse tipo, sem o qual o aplicativo acaba inoperante. Para garantir o funcionamento de um aplicativo moderno, são necessárias grandes quantidades de código JS que, antes que realmente funcionem, precisam ser analisadas e executadas. E essas são tarefas que dependem muito dos recursos do processador.
Indicador de desempenho
Usaremos um indicador da velocidade dos fragmentos de código como o número de instruções do processador necessárias para processá-los. Isso nos permitirá separar as medidas das propriedades de um computador em particular e do estado em que ele está no momento da medição. As métricas baseadas no tempo (como o TTI) têm muito "ruído". Eles dependem do estado da conexão de rede, bem como de qualquer outra coisa que acontece no computador no momento da medição. Por exemplo, alguns scripts executados durante o carregamento da página investigada, ou vírus ocupados com algo nos processos em segundo plano, podem afetar os indicadores de desempenho temporal. O mesmo pode ser dito sobre as extensões do navegador, que podem consumir muitos recursos do sistema e diminuir a velocidade da página. Ao calcular o número de instruções do processador, por outro lado, o tempo não importa. Tais indicadores podem ser, como você verá em breve, verdadeiramente estáveis.
Idéia
Aqui está a idéia subjacente ao nosso trabalho: precisamos criar um "laboratório" no qual o código será lançado e examinado quando forem feitas alterações nele. Por "laboratório", quero dizer um computador comum, talvez aquele que você usa constantemente. Os sistemas de controle de versão nos disponibilizam ganchos com os quais você pode interceptar determinados eventos e executar determinadas verificações. Obviamente, as medições no "laboratório" podem ser realizadas após o envio. Mas você provavelmente sabe que as alterações no código que atingiu o estágio de confirmação serão feitas mais lentamente do que no código que está sendo gravado (se houver). O mesmo se aplica à correção do código beta do produto e à correção do código que entrou em produção.
Precisamos que cada vez que o código for alterado, seu desempenho seja comparado antes e depois das alterações. Ao fazer isso, nos esforçamos para investigar componentes isoladamente. Como resultado, poderemos ver claramente os problemas e saber exatamente onde eles surgem.
O bom é que esses estudos podem ser realizados em um navegador real, usando, por exemplo, o Puppeteer. Esta é uma ferramenta que permite controlar o navegador sem uma interface de usuário do Node.js.
Código de pesquisa para pesquisa
Para encontrar o código do estudo, podemos consultar qualquer guia de estilo ou qualquer sistema de design. Em geral, estamos satisfeitos com qualquer coisa que forneça exemplos breves e isolados do uso de componentes.
O que é um "guia de estilo"? Geralmente, é um aplicativo da web que demonstra todos os componentes ou "elementos básicos" dos elementos da interface do usuário que estão disponíveis para o desenvolvedor. Pode ser uma determinada biblioteca de componentes de terceiros ou algo criado por seus próprios esforços.
Enquanto procurava por esses projetos na Internet, deparei-me com um
tópico de discussão recente no Twitter que falava sobre bibliotecas relativamente novas de componentes React. Eu olhei para várias das bibliotecas mencionadas lá.
Não surpreendentemente, as bibliotecas modernas de alta qualidade são fornecidas com documentação que inclui exemplos de código de trabalho. Aqui estão algumas bibliotecas e componentes de
Button
implementados por seus meios. A documentação para essas bibliotecas contém exemplos do uso desses componentes. Estamos falando da biblioteca Chakra e da biblioteca Semantic UI React.
Documentação do Chakra dos componentes dos botõesUI semântica de botão React DocumentationÉ exatamente disso que precisamos. Estes são exemplos cujo código podemos examinar quanto ao consumo de recursos do processador. Exemplos semelhantes podem ser encontrados nas entranhas da documentação ou em comentários de código escritos no estilo JSDoc. Talvez, se você tiver sorte, encontrará esses exemplos, projetados como arquivos separados, por exemplo, na forma de arquivos de teste de unidade. Certamente será assim. Afinal, todos escrevemos testes de unidade. Certo?
Arquivos
Imagine, para demonstrar o método descrito de análise de desempenho, que existe um componente
Button
na biblioteca que estamos estudando, cujo código está no arquivo
Button.js
arquivo com o teste de unidade
Button-test.js
está anexado a esse arquivo, bem como um arquivo com um exemplo de uso do componente -
Button-example.js
. Precisamos criar algum tipo de página de teste, no ambiente em que o código de teste pode ser executado. Algo como
test.html
.
Componente
Aqui está um componente simples do
Button
. Eu uso o React aqui, mas seus componentes podem ser gravados usando qualquer tecnologia conveniente para você.
import React from 'react'; const Button = (props) => props.href ? <a {...props} className="Button"/> : <button {...props} className="Button"/> export default Button;
Exemplo
E aqui está um exemplo de como usar o componente
Button
. Como você pode ver, neste caso, existem duas opções de componentes que usam propriedades diferentes.
import React from 'react'; import Button from 'Button'; export default [ <Button onClick={() => alert('ouch')}> Click me </Button>, <Button href="https://reactjs.com"> Follow me </Button>, ]
Teste
Aqui está a página
test.html
que pode carregar qualquer componente. Observe que o método chama o objeto de
performance
. É com a ajuda deles que, a nosso pedido, escrevemos no arquivo de log de desempenho do Chrome. Muito em breve usaremos esses registros.
const examples = await import(location.hash + '-example.js'); examples.forEach(example => performance.mark('my mark start'); ReactDOM.render(<div>{example}</div>, where); performance.mark('my mark end'); performance.measure( 'my mark', 'my mark start', 'my mark end'); );
Corredor de teste
Para carregar uma página de teste no Chrome, podemos usar a biblioteca
Puppeteer Node.js, que nos dá acesso à API para gerenciar o navegador. Você pode usar esta biblioteca em qualquer sistema operacional. Ele possui sua própria cópia do Chrome, mas também pode ser usado para trabalhar com uma instância do Chrome ou Chromium de várias versões já existentes no computador do desenvolvedor. O Chrome pode ser iniciado para que sua janela fique invisível. Os testes são realizados automaticamente, enquanto o desenvolvedor não precisa ver a janela do navegador. O Chrome pode ser iniciado no modo normal. Isso é útil para fins de depuração.
Aqui está um exemplo de script do Node.js. executado na linha de comando que carrega uma página de teste e grava dados em um arquivo de log de desempenho. Tudo o que acontece no navegador entre os comandos
tracing.start()
e
end()
é gravado (quero observar com detalhes) no arquivo
trace.json
.
import pup from 'puppeteer'; const browser = await pup.launch(); const page = await browser.newPage(); await page.tracing.start({path: 'trace.json'}); await page.goto('test.html#Button'); await page.tracing.stop(); await browser.close();
O desenvolvedor pode gerenciar os "detalhes" dos dados de desempenho especificando as "categorias" do rastreamento. Você pode ver a lista de categorias disponíveis se acessar o Chrome em
chrome://tracing
, clique em
Record
e abra a seção
Edit categories
na janela exibida.
Configurando a composição dos dados gravados no log de desempenhoResultados
Após a página de teste ser examinada usando o Puppeteer, você poderá analisar os resultados das medições de desempenho acessando o navegador em
chrome://tracing
e
trace.json
o download do arquivo
trace.json
recém-gravado.
Visualização Trace.jsonAqui você pode ver os resultados da chamada do método
performance.measure('my mark')
. A chamada
measure()
é apenas para fins de depuração, caso o desenvolvedor deseje abrir o arquivo
trace.json
e vê-lo. Tudo o que aconteceu com a página está incluído no bloco
my mark
.
Aqui está um
trace.json
:
Fragmento do arquivo trace.jsonPara descobrir o que precisamos, basta subtrair o indicador do número de instruções do processador (número de
ticount
) do marcador
Start
do mesmo indicador do marcador
End
. Isso permite que você descubra quantas instruções do processador são necessárias para exibir o componente no navegador. Esse é o mesmo número que você pode usar para descobrir se um componente se tornou mais rápido ou mais lento.
O diabo está nos detalhes
Agora, medimos apenas indicadores que caracterizam a primeira saída na página de um único componente. E nada mais. É imperativo medir indicadores relacionados à menor quantidade de código que pode ser executada. Isso permite reduzir o nível de "ruído". O diabo está nos detalhes. Quanto menor o desempenho medido, melhor. Após as medições, é necessário remover dos resultados obtidos o que está além da influência do desenvolvedor. Por exemplo, dados relacionados a operações de coleta de lixo. O componente não controla essas operações. Se eles são executados, isso significa que o navegador, no processo de renderização do componente, decidiu iniciá-los. Como resultado, os recursos do processador que foram para a coleta de lixo devem ser removidos dos resultados finais.
O bloco de dados relacionado à coleta de lixo (esse "bloco de dados" é mais corretamente chamado de "evento") é chamado
V8.GCScavenger
. Seu
tidelta
deve ser subtraído do número de instruções do processador que renderizam o componente.
Aqui está a documentação para eventos de rastreamento. É verdade que está desatualizado e não contém informações sobre os indicadores de que precisamos:
tidelta
- o número de instruções do processador necessárias para processar um evento.- número da
ticount
- o número de instruções para iniciar o evento.
Você precisa ter muito cuidado com o que estamos medindo. Navegadores são entidades altamente inteligentes. Eles otimizam o código que é executado mais de uma vez. No próximo gráfico, você pode ver o número de instruções do processador necessárias para gerar um determinado componente. A primeira operação de renderização requer mais recursos. As operações subsequentes criam uma carga muito menor no processador. Isso deve ser lembrado ao analisar o desempenho do código.
10 operações de renderização do mesmo componenteAqui está outro detalhe: se o componente executar algumas operações assíncronas (por exemplo, ele usa
setTimeout()
ou
fetch()
)), a carga no sistema criada pelo código assíncrono não é levada em consideração. Talvez seja bom. Talvez seja ruim. Se você estiver investigando o desempenho desses componentes, considere um estudo separado de código assíncrono.
Sinal forte
Se você adotar uma abordagem responsável para resolver o problema do que exatamente está sendo medido, poderá receber um sinal verdadeiramente estável que reflete o impacto no desempenho de quaisquer alterações. Adoro a suavidade das linhas no próximo gráfico.
Resultados de medição estáveisO gráfico inferior mostra os resultados da medição de 10 operações de renderização de um simples elemento
<span>
no React. Nada mais está incluído nesses resultados. Acontece que esta operação requer de 2,15 a 2,2 milhões de instruções do processador. Se você colocar o
<span>
na
<p>
, para gerar esse design, você precisará de cerca de 2,3 milhões de instruções. Esse nível de precisão me impressiona. Se um desenvolvedor pode ver a diferença de desempenho que aparece quando um único elemento
<p>
é adicionado a uma página, isso significa que o desenvolvedor tem uma ferramenta realmente poderosa em suas mãos.
Como exatamente representar medidas de tal precisão depende do desenvolvedor. Se ele não precisar de tal precisão, ele sempre poderá medir o desempenho da renderização de fragmentos maiores.
Informações adicionais de desempenho
Agora que o desenvolvedor tem à sua disposição um sistema para encontrar indicadores numéricos que caracterizam com precisão o desempenho dos menores fragmentos de código, o desenvolvedor pode usar esse sistema para resolver vários problemas. Portanto, usando
performance.mark()
você pode escrever informações úteis adicionais para
trace.json
. Você pode dizer aos membros da equipe de desenvolvimento o que está acontecendo e o que causa um aumento no número de instruções do processador necessárias para executar algum código. É possível incluir nos relatórios de desempenho informações sobre o número de nós DOM ou sobre o número de operações de gravação no DOM realizadas pelo React. De fato, aqui você pode exibir informações sobre muito. Você pode contar o número de recálculos do layout da página. Usando o Puppeteer, você pode capturar imagens das páginas e comparar a aparência da interface antes e depois de fazer alterações. Às vezes, o aumento no número de instruções do processador necessárias para exibir uma página parece completamente surpreendente. Por exemplo, se 10 botões e 12 campos para editar e formatar texto forem adicionados à nova versão da página.
Sumário
É possível para todos que foram discutidos aqui usá-lo hoje? Sim você pode. Para fazer isso, você precisa do Chrome versão 78 ou superior. Se
trace.json
tiver
tidelta
e de
tidelta
, o
tidelta
acima estará disponível. As versões anteriores do Chrome não.
Infelizmente, informações sobre o número de instruções do processador não podem ser obtidas na plataforma Mac. Ainda não testei o Windows, por isso não posso dizer nada definitivo sobre esse sistema operacional. Em geral - nossos amigos são Unix e Linux.
Deve-se notar que, para que o navegador possa fornecer informações sobre as instruções do processador, você precisará usar alguns sinalizadores - estes são
--no-sandbox
e
--enable-thread-instruction-count
. Veja como passá-los para um navegador lançado pelo Puppeteer:
await puppeteer.launch({ args: [ '--no-sandbox', '--enable-thread-instruction-count', ]});
Espero que agora você possa levar sua análise de desempenho de aplicativos da web para o próximo nível.
Caros leitores! Você planeja usar a metodologia para analisar o desempenho dos projetos da web aqui apresentados?
