Recentemente, compartilhei uma excelente palestra, do Feredico Lois, colega no desenvolvimento do RavenDB, sobre padrões para alta performance com C#. Ele começa a palestra falando sobre três estratégias para otimização que gostaria de abordar aqui.
1. Pareto – 20% do código consome 80% dos recursos
Essa é a condição da maioria das aplicações.
Boa parte das vezes em que sou chamado para consultoria percebo que há oportunidade para trabalhar e obter excelentes resultados de performance revisando:
- arquitetura – componentes mal planejados e desnecessariamente acoplados, que exigem interação constante com os demais componentes gerando overhead assombroso. Desperdício de recursos computacionais disponíveis.
- implicações da rede e/ou I/O – muito comum! desenvolvedores ignoram as falácias da computação distribuída, por ter “tudo carregado na máquina” durante o desenvolvimento.
- algoritmos ou falhas de implementação – abordagens infelizes – mal implementadas ou selecionadas – que ignoram características do ambiente (como o Garbage Collector).
Geralmente, é possível produzir grandes melhorias com pouco esforço relativo.
No RavenDB, por exemplo, revisitamos a forma como tratamos a alocação de memória. No Roslyn, a Microsoft adotou estratégias muito inteligentes de caching, …
Há pouco tempo, em um cliente, ajudei a reduzir a alocação de memória – o que ajudou a reduzir a pressão sobre o GC – e melhoramos a interação entre componentes. O tempo de processamento crítico caiu em 85%.
Em outro cliente, verifiquei que uma aplicação estava fazendo mais de cem requisições para compor uma tela! Reduzimos para 10 (ainda acho muito)! Tivemos ganho de 47%.
Em outro cliente, percebi que a tela principal do sistema renderizava uma quantidade absurda de gráficos e indicadores que, … ninguém olhava. Cortamos! Ganho de 85%.
Em outro caso, verificamos que boa parte do processamento que estava ocorrendo de forma sequencial poderia ser executada de forma paralela. Reduzimos o tempo de processamento para 1/4 do tempo original.
Recenetemente, ajudei uma grande empresa de simulações a levar processamento da CPU para a GPU. Baixamos o tempo de processamento de uma área crítica de 3 horas para 27 segundos.
2. Pareto2 – 4% do seu código consome 64% dos recursos
Quando a arquitetura está bem definida, a rede é “respeitada” e o código está bem escrito (respeitando o GC, sobretudo), o caminho para a otimização está na revisão dos argoritmos e das estruturas de dados utilizadas.
No RavenDB, por exemplo, substituímos a forma como representamos JSON na memória e isso reduziu a complexidade de acesso a dados de O(n log n) para O(1). Foi uma das mudanças que tornou o RavenDB4 10x mais rápido que as versões anteriores.
O esforço para revisão de algoritmos é de complexidade média. Os ganhos podem ser enormes!
3. Pareto3 – 0,8% do seu código consome 51% dos recursos
Quando a arquitetura está bem definida, a rede é “respeitada”, o código está bem escrito (respeitando o GC, sobretudo), e os algoritmos certos foram selecionados, … é hora de partir para micro otimizações.
Repare que o espaço para ganho real com micro otimizações está em uma parcela muito pequena do seu código (geralmente, menos de 1% da base total).
O uso de micro otimizações implica em profundo conhecimento de como o código será executado pelo computador e, em decorrência disso, grande intendimento sobre como o computador e o sistema operacional funcionam.
Micro otimizações geralmente levam a código de difícil leitura e, consequentemente, manutenção.
O esforço para implementar micro otimizações tem custo bem alto. Os ganhos são percebidos e justificam os custos apenas em raras situações.
Considerações finais
Se você está com problemas de performance em sua aplicação, a recomendação é seguir o seguinte caminho:
- Revise a arquitetura de sua aplicação garantindo que os componentes estão coesos
- Respeite a rede! Tudo que exigir um request representa custo elevado.
- Respeite o disco! Tudo que exigir operação de escrita ou leitura tem custo elevado.
- Respeite as características de sua linguagem/framework! Garanta que não está, por exemplo, criando mais strings que o necessário.
- Revise a seleção de algoritmos e, principalmente, as estruturas de dados que está adotando.
- Em casos extremos (apenas em casos extremos), busque oportunidades de micro otimização.
No mais, conte comigo! Posso ajudar você e sua empresa a resolver problemas de performance.
Capa: Jason Chen