Quando ganhos de desempenho impressionantes não importam
(blog.colinbreck.com)- A otimização de desempenho é uma forma poderosa de entender sistemas complexos e melhorar produtos, mas mesmo um resultado 10 vezes mais rápido pode não mudar a forma real de trabalho nem a vazão
- Mesmo reduzindo a resposta de consultas de 5–10 minutos para 30 segundos–1 minuto, se ela ultrapassa o limiar de cerca de 10 segundos em que uma pessoa consegue esperar mantendo a atenção, o efeito percebido é limitado
- Quando o trabalho é agrupado em unidades inteiras, como 1 ou 2 tarefas por dia, uma melhoria de 25–50% não basta; incluindo o tempo de deslocamento, cada tarefa precisa levar 4 horas ou menos para que seja possível fazer 2 por dia
- Em pipelines de dados, uma etapa lenta aplica backpressure nas etapas upstream; por isso, mesmo que uma única etapa fique muito mais rápida, a vazão de ponta a ponta pode não aumentar até que todos os gargalos sejam eliminados
- Melhorias de desempenho devem ser avaliadas pelo resultado desejado, não por benchmarks isolados; se não superarem restrições como manutenção da atenção, aumento das unidades de trabalho ou vazão total, mesmo grandes melhorias terão pouco efeito prático
Por que métricas de desempenho e resultados reais divergem
- Trabalhar em desempenho é gratificante porque pode tornar sistemas mais eficientes e abrir novas possibilidades para clientes
- Também ajuda a entender empiricamente como sistemas complexos interagem sob escala e carga
- Ao lidar de perto com o sistema, surgem ideias para melhorar produtos e serviços, e algumas delas não têm relação direta com otimização de desempenho
- Ainda assim, resultados que parecem bons, como “10 vezes mais rápido” ou “50% menos recursos”, podem não produzir a mudança esperada se não superarem restrições ocultas
Acima de 10 segundos, a atenção do usuário se perde
- Há um caso em que a performance de consultas de um novo banco de dados foi melhorada, reduzindo as consultas mais caras no banco anterior de 5–10 minutos para 30 segundos–1 minuto
- Esse resultado representou uma melhoria da ordem de 10 vezes, mas, para mudar a experiência do usuário, seria necessária mais uma grande melhoria
- Em estudos de interação humano-computador, o limite para uma pessoa manter a atenção na tarefa completa é considerado de cerca de 10 segundos
- 0,1 segundo é o limiar percebido como feedback imediato
- Cerca de 1 segundo é o limiar para manter o fluxo da tarefa
- Cerca de 10 segundos é o limiar para manter a atenção na tarefa como um todo
- Feedback como indicadores de progresso ou tempo estimado pode ajudar a manter a atenção
- Tanto 30 segundos quanto 5 minutos passam de 10 segundos, então o usuário verifica mensagens, vai tomar café, começa uma conversa ou troca para outra tarefa
- Quando o usuário volta minutos ou horas depois e a UI já está carregada, o fato de o tempo real de espera ter sido 30 segundos ou 5 minutos não faz grande diferença na forma de trabalho
- Nesse projeto, no fim, muitas consultas foram reduzidas para 10 segundos ou menos, e algumas consultas que antes eram impossíveis por timeout passaram a ser viáveis
- Além da latência das consultas de dados, a latência das consultas de metadados e o tempo de renderização da página web também foram importantes para a melhoria geral de desempenho
- Ainda há a possibilidade de mais uma melhoria de 10 vezes com IO assíncrono e melhorias na agregação de dados; com isso, consultas que antes levavam minutos poderiam passar a rodar em menos de 1 segundo
O limiar para passar de 1 para 2 tarefas por dia
- Em um projeto, por meio da automação de trabalho manual, remoção de etapas desnecessárias, alguma paralelização e adiamento de etapas que poderiam ser tratadas depois de forma assíncrona, o processo completo foi reduzido de várias horas para, de forma confiável, menos de 1 hora
- A melhoria foi de cerca de 25–50%, mas o processo geral não mudou por causa de restrições logísticas
- Dá para pensar em casos como encanadores, eletricistas ou marceneiros, que precisam reservar um local, deslocar-se até lá e concluir o trabalho
- Se a jornada é de 8 horas e o trabalho em um local leva 8 horas, economizar 2–3 horas não deixa tempo para ir a um novo local e terminar um novo trabalho
- Incluindo o tempo de deslocamento, se cada trabalho não levar 4 horas ou menos, não é possível concluir 2 por dia
- Até superar esse limiar, melhorias de eficiência em etapas intermediárias não se traduzem em aumento de produção
- O foco em desempenho também pode trazer melhorias de qualidade e estabilidade que afetam diretamente a experiência do cliente
- Mesmo pequenas melhorias de desempenho que não sejam decisivas em produção podem acelerar ciclos de repetição no ambiente de testes, tornando o desenvolvimento de funcionalidades e a correção de defeitos mais rápidos
Gargalos em pipelines com backpressure
- Muitas infraestruturas de software de negócios incluem pipelines de dados que processam eventos de várias fontes, como veículos, equipamentos industriais, celulares e transações financeiras
- Em geral, os eventos são armazenados em logs duráveis, e serviços downstream os consomem e processam
- Para alta vazão em grande escala, os logs precisam ser particionados, e os serviços downstream usam técnicas como batching, pipelining, paralelismo, alocação eficiente de memória e escalabilidade dinâmica
- Gargalos em pipelines de dados são difíceis de encontrar porque o comportamento das partes do sistema é interdependente
- Uma etapa lenta aplica backpressure intencionalmente nas etapas upstream
- Quando há vários gargalos, a vazão total não aumenta até que o último gargalo seja removido
- Dividir o pipeline em etapas e entender as características e limites de desempenho de cada uma é uma boa prática de engenharia
- Porém, mesmo melhorar uma única etapa em várias ordens de grandeza pode não afetar a vazão total
- Na melhoria de vazão, a métrica importante não é o benchmark de uma etapa isolada, mas a vazão de ponta a ponta
Uma abordagem empírica para encontrar gargalos
- Para entender a dinâmica e os gargalos desses sistemas, é útil verificar empiricamente a partir do início do pipeline
- Por exemplo, começar pela etapa que lê eventos de um log distribuído e os descarta
- Se só essa etapa não atingir a vazão-alvo, otimizar etapas downstream será perda de tempo
- Benchmarks downstream, como quantas linhas por segundo podem ser inseridas em um banco de dados, também podem ser importantes, mas a análise deve começar pelo upstream
- Simulações também são uma forma valiosa de entender sistemas complexos e desempenho
O critério para melhorias de desempenho é o resultado desejado
- Trabalhar em desempenho é difícil, mas também é um treinamento para entender profundamente sistemas complexos e criar produtos melhores
- Se é preciso prender a atenção das pessoas, a resposta deve chegar em cerca de 10 segundos
- Se a unidade de trabalho total é a restrição, melhorias percentuais não bastam; é preciso conseguir passar de 1 tarefa para 2 tarefas por dia
- Para maximizar a vazão de um pipeline com backpressure, muitas vezes é necessário resolver todos os gargalos, não apenas um ou dois
- Se essas restrições não forem superadas, até uma melhoria de desempenho da ordem de 10 vezes pode não produzir o resultado desejado
1 comentários
Comentários no Lobste.rs
Se você melhora dezenas de vezes uma etapa que não afeta a vazão total e acaba se decepcionando, aqui vale mencionar a lei de Amdahl
Continuo ouvindo falar de novas leis, mas elas são nichadas o suficiente para não serem usadas com frequência, então acabo esquecendo de novo. Isso não quer dizer que essas leis não sejam válidas ou úteis como conhecimento geracional
Para começo de conversa, fico me perguntando por que estavam otimizando uma parte que representa apenas um pedacinho minúsculo do tempo total
Não sei se a orientação era ruim ou se faltavam ferramentas de performance. Pouca gente tenta conscientemente trabalhar em algo que quase não tem impacto; normalmente há um problema maior escondido
Você está investigando um problema, vê uma função aparecer bastante nos resultados de amostragem e, dando uma olhada rápida, parece que a implementação pode ser melhorada com certa facilidade. Mas, como está fazendo outra coisa no momento, deixa para “resolver depois”
Mais tarde, você lembra daquela melhoria fácil e começa, mas, mesmo quando a mudança acaba sendo mais complicada do que parecia, surge uma visão de túnel e a vontade de resolver o quebra-cabeça, então acaba gastando muito tempo
Na prática, aquele problema de performance era irrelevante. Talvez parecesse grande proporcionalmente dentro do contexto que você estava analisando na época, talvez o tempo absoluto não significasse muita coisa, ou talvez o gargalo fosse I/O e não CPU. É quase uma espécie de nerd sniping contra si mesmo
Mesmo assim, a otimização foi feita, e a pessoa ficou surpresa quando a melhoria total saiu abaixo de 0,1%. Com conhecimento do domínio, dava para intuir que não era uma parte cara, mas ainda ajudamos até a medir o custo real de performance para evitar desperdício de tempo
Ou talvez o benchmark tenha levado a uma interpretação equivocada. Nem todo sistema mostra facilmente, em produção, o custo de todos os métodos ou processos
Todas essas explicações, em graus diferentes, se aproximam de alguma disfunção; algumas são mais um problema individual, outras são mais um problema do ambiente
3.1. Se você trabalha em um hyperscaler, uma redução de 0,1% na computação, totalmente invisível para o usuário, pode impactar o resultado financeiro o bastante para pagar uma casa na praia
Muitas vezes, as práticas comuns de programação em si tornam tudo lento de forma geral. Por exemplo, penso em código orientado a objetos cheio de ponteiros e com uma quantidade enorme de alocações no heap via alocadores genéricos. Qualquer ponto que você corrija será apenas uma pequena fração do tempo total; no pior caso, não há solução além de uma reescrita completa. Com sorte, dá para reescrever de forma incremental
Mesmo que haja só dois gargalos, se eles estiverem dentro de uma ordem de grandeza um do outro, corrigir perfeitamente o pior gargalo nem sequer produzirá uma melhoria de uma ordem de grandeza. Em muitos casos, como o texto diz, para obter um ganho significativo é preciso ir muito além disso
Além disso, computadores são mais parecidos com sistemas distribuídos rodando em paralelo. Há várias CPUs, GPUs, discos, Ethernet etc., e a velocidade do processo é limitada pela etapa mais lenta do pipeline. Ao corrigir essa etapa, a próxima etapa mais lenta passa a limitar; no pior caso, há várias etapas mais lentas empatadas, e corrigir apenas uma não traz ganho algum
Ainda assim, essa é uma interpretação generosa; às vezes a pessoa simplesmente se perde no jogo da otimização e perde as prioridades, ou comete erros
Mesmo que o usuário não perceba, reduzir o tempo de computação do software continua sendo bom
Porque pode reduzir custos e facilitar a escalabilidade
Se tornar o código mais rápido introduz complexidade excessiva, isso gera consequências posteriores que prejudicam a performance no longo prazo, mesmo deixando a manutenibilidade de lado. Ao mudar a arquitetura, código que agora se tornou desnecessário permanece, e para entendê-lo ou removê-lo é preciso compreender completamente toda a complexidade
O fato de algo “ficar mais rápido” não deve virar uma carta branca para ignorar preocupações com complexidade ou manutenibilidade
Estou em uma situação parecida agora, reduzindo uma query que levava 5 minutos para menos de 30 segundos por meio de vários pequenos projetos
Digo à equipe que, no longo prazo, isso não será suficiente, mas claramente é uma melhoria e tem grande impacto
Para o cliente, a espera cai de um nível enlouquecedor para apenas irritante
O foco agora não é a performance por usuário, mas a performance geral. Ao otimizar dezenas de processos de 5, 10 ou 30 minutos, a contenção com outras partes do sistema diminui bastante. Ficar batendo no banco de dados por 10 minutos é tempo demais e, no fim, tudo fica mais rápido, então todos saem ganhando