Métricas de desempenho para pesquisar aplicativos da Web incrivelmente rápidos

Há um ditado: "O que você não pode medir, não pode melhorar". O autor do artigo, cuja tradução estamos publicando hoje, trabalha para Superhuman . Ele diz que esta empresa está desenvolvendo o cliente de email mais rápido do mundo. Aqui falaremos sobre o que é "rápido" e como criar ferramentas para medir o desempenho de aplicativos da Web incrivelmente rápidos.



Medição de velocidade da aplicação


Em um esforço para melhorar nosso desenvolvimento, passamos muito tempo medindo sua velocidade. E, como se viu, as métricas de desempenho são indicadores surpreendentemente difíceis de entender e aplicar.

Por um lado, é difícil projetar métricas que descrevam com precisão as sensações que o usuário experimenta enquanto trabalha com o sistema. Por outro lado, não é fácil criar métricas tão precisas que suas análises permitam que você tome decisões informadas. Como resultado, muitas equipes de desenvolvimento não podem confiar nos dados que coletam sobre o desempenho de seus projetos.

Mesmo que os desenvolvedores tenham métricas confiáveis ​​e precisas, usá-las não é fácil. Como definir o termo "rápido"? Como encontrar um equilíbrio entre velocidade e consistência? Como aprender a detectar rapidamente a degradação do desempenho ou a avaliar o impacto das otimizações no sistema?

Aqui, queremos compartilhar algumas idéias sobre o desenvolvimento de ferramentas de análise de desempenho de aplicativos da web.

1. Usando o "relógio" certo


O JavaScript possui dois mecanismos para recuperar registros de data e hora: performance.now() e new Date() .

Como eles diferem? As duas diferenças a seguir são fundamentais para nós:

  • O método performance.now() é muito mais preciso. A precisão do new Date() construto new Date() é de ± 1 ms, enquanto a precisão de performance.now() já é de ± 100 µs (sim, trata-se de microssegundos !).
  • Os valores retornados pelo método performance.now() sempre aumentam a uma taxa constante e são independentes da hora do sistema. Este método simplesmente mede intervalos de tempo sem focar na hora do sistema. E na new Date() hora new Date() sistema afeta. Se você reorganizar o relógio do sistema, ele também mudará o que new Date () retorna, e isso arruinará os dados de monitoramento de desempenho.

Embora os “relógios” representados pelo método performance.now() sejam obviamente muito mais adequados para medir intervalos de tempo, eles também não são ideais. Tanto o performance.now() quanto o new Date() sofrem do mesmo problema, que se manifesta no caso de o sistema estar em estado de suspensão: as medições incluem o tempo em que a máquina nem estava ativa.

2. Verificando a Atividade do Aplicativo


Se você, medindo o desempenho de um aplicativo Web, alterne de sua guia para outra - isso interromperá o processo de coleta de dados. Porque O fato é que o navegador restringe os aplicativos localizados nas guias em segundo plano.

Há duas situações nas quais as métricas podem ser distorcidas. Como resultado, o aplicativo parecerá muito mais lento do que realmente é.

  1. O computador entra no modo de suspensão.
  2. O aplicativo é executado na guia segundo plano do navegador.

A ocorrência de ambas as situações não é incomum. Felizmente, temos duas opções para resolvê-los.

Primeiro, podemos simplesmente ignorar métricas distorcidas, descartando resultados de medição que diferem muito de alguns valores razoáveis. Por exemplo, o código chamado quando um botão é pressionado simplesmente não pode ser executado por 15 minutos! Talvez seja a única coisa que você precisa para lidar com os dois problemas descritos acima.

Em segundo lugar, você pode usar a propriedade document.hidden e o evento privacychange . O evento de visibilitychange é aumentado quando o usuário alterna da guia de interesse do navegador para outra guia ou retorna para a guia de seu interesse. É chamado quando a janela do navegador minimiza ou maximiza quando o computador começa a trabalhar, saindo do modo de suspensão. Em outras palavras, é exatamente disso que precisamos. Além disso, desde que a guia esteja em segundo plano, a propriedade document.hidden é true .

Aqui está um exemplo simples que demonstra o uso da propriedade document.hidden e o evento visibilitychange .

 let lastVisibilityChange = 0 window.addEventListener('visibilitychange', () => {  lastVisibilityChange = performance.now() }) //    ,      , //  ,   ,     if (metric.start < lastVisibilityChange || document.hidden) return 

Como você pode ver, descartamos alguns dados, mas isso é bom. O fato é que esses são dados relacionados aos períodos do programa em que não é possível usar totalmente os recursos do sistema.

Agora conversamos sobre indicadores que não nos interessam. Mas há muitas situações, os dados coletados são muito interessantes para nós. Vamos ver como coletar esses dados.

3. Procure o indicador que permite capturar melhor a hora em que o evento começou


Um dos recursos mais controversos do JavaScript é que o loop de eventos para esse idioma é de thread único. Em um determinado momento, apenas um trecho de código é capaz de executar, cuja execução não pode ser interrompida.

Se o usuário pressionar o botão durante a execução de um determinado código, o programa não saberá até que a execução desse código seja concluída. Por exemplo, se o aplicativo gastou 1000 ms em um ciclo contínuo e o usuário pressionou o botão Escape 100 ms após o início do ciclo, o evento não será gravado por outros 900 ms.

Isso pode distorcer gravemente as métricas. Se precisarmos de precisão na medição exata de como o usuário percebe trabalhar com o programa, esse é um problema enorme!

Felizmente, resolver esse problema não é tão difícil. Se estamos falando sobre o evento atual, podemos, em vez de usar performance.now() (a hora em que vimos o evento), usar window.event.timeStamp (a hora em que o evento foi criado).

O registro de data e hora do evento é definido pelo processo principal do navegador. Como esse processo não bloqueia quando o loop de eventos JS está bloqueado, event.timeStamp fornece informações muito mais valiosas sobre quando o evento foi realmente disparado.

Deve-se notar que esse mecanismo não é ideal. Portanto, entre o momento em que o botão físico é pressionado e o momento em que o evento correspondente chega no Chrome, decorrem 9 a 15 ms de tempo não contabilizado ( aqui está um excelente artigo do qual você pode saber por que isso acontece).

No entanto, mesmo que possamos medir o tempo que leva para o evento chegar ao Chrome, não devemos incluir esse tempo em nossas métricas. Porque O fato é que não podemos introduzir essas otimizações no código que podem afetar significativamente esses atrasos. Não podemos melhorá-los de nenhuma maneira.

Como resultado, se falarmos em encontrar o registro de data e hora para o início do evento, o indicador event.timeStamp parecerá mais adequado aqui.

Qual é a melhor estimativa de quando o evento termina?

4. Desligue o cronômetro em requestAnimationFrame ()


Mais uma consequência segue os recursos do dispositivo de loop de eventos em JavaScript: algum código que não está relacionado ao seu código pode ser executado depois dele, mas antes que o navegador exiba uma versão atualizada da página na tela.

Considere, por exemplo, React. Depois de executar seu código, o React atualiza o DOM. Se você medir apenas o tempo no seu código, significa que não medirá o tempo que levou para executar o código React.

Para medir esse tempo extra, usamos requestAnimationFrame() para desligar o cronômetro. Isso é feito apenas quando o navegador está pronto para gerar o próximo quadro.

 requestAnimationFrame(() => { metric.finish(performance.now()) }) 

Aqui está o ciclo de vida do quadro (o diagrama é retirado deste material maravilhoso sob requestAnimationFrame ).


Ciclo de vida do quadro

Como você pode ver nesta figura, requestAnimationFrame() é chamado após a conclusão do processador, logo antes do quadro ser exibido. Se desligar o cronômetro aqui, significa que podemos ter certeza absoluta de que tudo o que levou algum tempo para atualizar a tela está incluído nos dados coletados no intervalo de tempo.

Até aí tudo bem, mas agora a situação está se tornando bastante complicada ...

5. Ignorando o tempo necessário para criar um layout de página e sua visualização.


O diagrama anterior, mostrando o ciclo de vida de um quadro, ilustra outro problema que encontramos. No final do ciclo de vida do quadro, existem blocos de Layout (formando um layout de página) e Paint (exibindo uma página). Se você não levar em consideração o tempo necessário para concluir essas operações, o tempo medido por nós será menor que o tempo necessário para que alguns dados atualizados apareçam na tela.

Felizmente, requestAnimationFrame tem outro ás na manga. Quando a função passada por requestAnimationFrame chamada, um registro de data e hora é passado para essa função, indicando o horário de início da formação do quadro atual (ou seja, aquele localizado na parte esquerda do diagrama). Esse registro de data e hora geralmente está muito próximo do horário final do quadro anterior.

Como resultado, a desvantagem acima pode ser corrigida medindo o tempo total decorrido desde o momento do event.timeStamp até a hora do início da formação do próximo quadro. Observe o requestAnimationFrame aninhado:

 requestAnimationFrame(() => {  requestAnimationFrame((timestamp) => { metric.finish(timestamp) }) }) 

Embora o que é mostrado acima pareça uma excelente solução para o problema, no final, decidimos não usar esse design. O fato é que, embora essa técnica permita obter dados mais confiáveis, a precisão desses dados é reduzida. Os quadros no Chrome são formados com uma frequência de 16 ms. Isso significa que a maior precisão disponível para nós é de ± 16 ms. E se o navegador estiver sobrecarregado e pular quadros, a precisão será ainda menor e essa deterioração será imprevisível.

Se você implementar esta solução, uma grande melhoria no desempenho do seu código, como acelerar uma tarefa que foi executada anteriormente 32 ms, até 15 ms, pode não afetar os resultados da medição de desempenho.

Sem levar em consideração o tempo necessário para criar um layout de página e sua saída, obtemos métricas muito mais precisas (± 100 μs) para o código que está sob nosso controle. Como resultado, podemos obter uma expressão numérica de qualquer melhoria feita nesse código.

Também exploramos uma ideia semelhante:

 requestAnimationFrame(() => {  setTimeout(() => { metric.finish(performance.now()) } }) 

Isso incluirá o tempo de renderização, mas a precisão do indicador não será limitada a ± 16 ms. No entanto, decidimos não usar essa abordagem também. Se o sistema encontrar um evento de entrada longo, a chamada para qual setTimeout transmitido poderá ser significativamente atrasada e executada após a atualização da interface do usuário.

6. Esclarecimento da “porcentagem de eventos abaixo da meta”


Estamos desenvolvendo um projeto e focando no alto desempenho, tentando otimizá-lo de duas maneiras:

  1. Velocidade. O tempo de execução da tarefa mais rápida deve ser o mais próximo possível de 0 ms.
  2. Uniformidade. O tempo de execução da tarefa mais lenta deve ser o mais próximo do tempo de execução da tarefa mais rápida.

Devido ao fato de esses indicadores mudarem ao longo do tempo, eles são difíceis de visualizar e não são fáceis de discutir. É possível criar um sistema de visualização desses indicadores que nos inspiraria a otimizar velocidade e uniformidade?

Uma abordagem típica é medir o percentil 90 de atraso. Essa abordagem permite desenhar um gráfico de linhas ao longo do eixo Y, no qual o tempo em milissegundos é salvo. Este gráfico permite ver que 90% dos eventos estão abaixo do gráfico de linhas, ou seja, eles são executados mais rapidamente do que o tempo indicado pelo gráfico de linhas.

Sabe-se que 100 ms é o limite entre o que é percebido como "rápido" e "lento".

Mas o que descobriremos sobre como os usuários se sentem no trabalho se soubermos que o 90º percentil de atraso é de 103 ms? Nem tanto. Quais indicadores fornecerão aos usuários usabilidade? Não há como saber isso com certeza.

Mas e se soubermos que o 90º percentil de atraso é de 93 ms? Há um sentimento de que 93 é melhor que 103, mas não podemos dizer mais nada sobre esses indicadores, bem como o que eles significam em termos de percepção do projeto pelo usuário. Novamente, não há resposta exata para essa pergunta.

Nós encontramos uma solução para este problema. Consiste em medir a porcentagem de eventos cujo tempo de execução não excede 100 ms. Há três grandes vantagens nessa abordagem:

  • A métrica é orientada ao usuário. Ela pode nos dizer qual a porcentagem de tempo que nosso aplicativo é rápido e qual a porcentagem de usuários que o percebem tão rápido.
  • Essa métrica nos permite retornar as medições com a precisão perdida devido ao fato de não termos medido o tempo necessário para concluir as tarefas no final do quadro (falamos sobre isso na seção No. 5). Devido ao fato de definirmos um indicador de meta que se encaixa em vários quadros, os resultados das medições que se aproximam desse indicador acabam sendo inferiores a ele ou mais.
  • Essa métrica é mais fácil de calcular. Basta calcular o número de eventos cujo tempo de execução está abaixo do indicador de destino e depois disso - dividi-los pelo número total de eventos. Os percentis são muito mais difíceis de contar. Existem aproximações eficazes, mas para fazer tudo certo, você precisa levar em consideração cada dimensão.

Essa abordagem tem apenas um sinal de menos: se os indicadores forem piores que a meta, não será fácil perceber a melhora.

7. O uso de vários limiares na análise de indicadores


Para visualizar o resultado da otimização de desempenho, introduzimos vários valores limite adicionais em nosso sistema - acima de 100 ms e abaixo.

Agrupamos os atrasos assim:

  • Menos de 50 ms (rápido).
  • 50 a 100 ms (bom).
  • 100 a 1000 ms (lento).
  • Mais de 1000 ms (terrivelmente lento).

Resultados "terrivelmente lentos" nos permitem ver que perdemos muito em algum lugar. Portanto, destacamos em vermelho brilhante.

O que cabe em 50 ms é muito sensível a alterações. Aqui, as melhorias de desempenho geralmente são visíveis muito antes de serem vistas em um grupo que corresponde a 100 ms.

Por exemplo, o gráfico a seguir visualiza o desempenho da exibição de threads no Superhuman.


Ver tópico

Ele mostra o período de declínio do desempenho e, em seguida, os resultados das melhorias. É difícil avaliar a queda no desempenho se você observar apenas os indicadores correspondentes a 100 ms (as partes superiores das colunas azuis). Ao analisar os resultados que se encaixam em 50 ms (as partes superiores das colunas verdes), os problemas de desempenho já são visíveis muito mais claramente.

Se usássemos a abordagem tradicional para o estudo de métricas de desempenho, provavelmente não teríamos notado um problema cujo efeito no sistema é mostrado na figura anterior. Mas, graças à maneira como realizamos medições e visualizamos nossas métricas, conseguimos encontrar e resolver rapidamente um problema.

Sumário


Verificou-se que era surpreendentemente difícil encontrar a abordagem correta para trabalhar com métricas de desempenho. Conseguimos desenvolver uma metodologia que nos permite criar ferramentas de alta qualidade para medir o desempenho de aplicativos da web. Ou seja, estamos falando sobre o seguinte:

  1. A hora de início de um evento é medida usando event.timeStamp .
  2. O horário de término do evento é medido usando performance.now() no retorno de chamada passado para requestAnimationFrame() .
  3. Tudo o que acontece com o aplicativo enquanto ele está na guia inativa do navegador é ignorado.
  4. Os dados são agregados usando um indicador, que pode ser descrito como "a porcentagem de eventos que estão abaixo da meta".
  5. Os dados são visualizados com vários níveis de valores limite.

Essa técnica fornece as ferramentas para criar métricas confiáveis ​​e precisas. Você pode criar gráficos que mostram claramente uma queda no desempenho e visualizar os resultados das otimizações. E o mais importante - você tem a oportunidade de criar projetos rápidos ainda mais rapidamente.

Caros leitores! Como você analisa o desempenho de seus aplicativos da web?


Source: https://habr.com/ru/post/pt470872/


All Articles