mundo.IT

Carlos Ribeiro

Dicas (muito) simples de otimização de performance

Ontem um amigo me consultou a respeito de um problema de performance em uma aplicação Python. Apesar de não estar programando ativamente já faz um tempo, continuo "brincando" de vez em quando; além do mais, muitas vezes é mais importante saber pensar em termos de arquitetura do que em detalhes de implementação. Fora isso, com o passar do tempo a gente acumula muitas técnicas que às vezes usa sem pensar. Esse foi o caso ontem.

Correndo o risco de "ensinar o pai nosso ao vigário", vamos com a primeira parte. A princípio qualquer aplicação pode ser segmentada para dividir a carga. Existem duas formas principais de fazer isso. Uma divisão vertical equivale a quebrar o processo em "camadas", cada uma com uma função. Cada camada é feita por um servidor separado. Já uma divisão horizontal equivale a dividir as requisições entre vários servidores que fazem a mesma função.

As duas formas de divisão não são mutuamente exclusivas. Você pode quebrar uma aplicação na vertical, separando as partes do processo, e depois, quebrar cada uma das camadas horizontalmente, dividindo a carga entre vários servidores. As duas formas são válidas mas tem vantagens e desvantagens próprias.

Para começar, qualquer tipo de divisão torna o sistema mais complexo. No sentido horizontal, lidar com paralelismo não é fácil. Se houver concorrência de acesso entre os vários processos, é preciso pensar em sincronização, "locking", etc. Já no sentido vertical, é preciso pensar na comunicação entre processos, possibilidade de falhas, etc. Nos dois casos a programação fica mais complicada, mas cada caso é um caso.

No caso da segmentação horizontal, a maior vantagem é a escalabilidade. Se a carga aumentar (e se o programa estiver bem escrito), basta adicionar servidores. A coordenação da divisão de carga precisa ser bem pensada, mas se houver um bom desacoplamento entre os servidores, o resultado é excelente. Já a segmentação vertical facilita o teste da aplicação "em partes" e portanto simplifica o desenvolvimento. Outra vantagem é o melhor isolamento de falhas. Também possibilita uma implementação mais segura, ao não expor os servidores das camadas internas diretamente.

No caso de ontem, a solução sugerida ficou simples.A aplicação pode ser quebrada em duas camadas, "front end" e "back end". O "front end" é basicamente um "proxy" que cuida de tarefas de segurança (assinatura digital das mensagens, etc.), que são computacionalmente pesadas e que impactam na implementação da funcionalidade do "back end". O restante da funcionalidade fica no "back end". A divisão também facilita o teste, pois é possível testar tanto o "front end" como o "back end" isoladamente. No futuro, a aplicação poderá crescer com a divisão horizontal nas duas camadas.

A segunda parte é bem simples. É uma técnica muito básica para ganhar performance que vale não só para programas em Python, mas também em outras linguagens. É o tipo da coisa que ás vezes a gente usa sem pensar: concatenação de strings.

A chamada do cálculo do "hash" de segurança da aplicação recebe uma mensagem composta de várias partes. Essas partes eram concatenadas manualmente no momento da chamada. Sugeri que ao invés de concatenar a string, que fosse feita uma passagem do hash com um "update" para cada componente, calculando o hash de forma incremental. Parece simples, mas o ganho de performance para uma simples concatenação a cada "hash" foi de 10%. Nada mal para um ajuste feito em 5 minutos.

A explicação para isso é simples, e é algo que muitos programadores nem param para pensar. Por toda sua simplicidade aparente, a operação de concatenação é uma das mais caras possíveis. Cada soma de strings implica em alocar espaço em memória para uma string nova e copiar os dados de todos os componentes. Quanto maior a string mais lento é o processo. Se você for concatenar uma mensagem para usar uma vez só e descartar o resultado, não faz sentido - e esse era o caso do "hash" citado acima.

Esse idioma (concatenar strings para passar como parâmetro) é muito comum, especialmente em aplicações de rede como servidores, etc. Na maioria das vezes os efeitos de performance são irrelevantes, vale a pena manter a concatenação porque fica mais compacto e legível. Porém, nesse tipo de aplicação, sempre que houver um problema de performance, é interessante avaliar em que lugares do código isso é realmente necessário.

Em resumo, conseguimos em uma conversa de meia hora tratar do problema da arquitetura e ganhar 10% de performance com uma mudança de cinco linhas. O melhor de tudo é que conseguimos manter as coisas no nível certo. Dizem que a "otimização prematura é a raiz de todo mal". Otimizamos somente o necessário, e no momento necessário. É a melhor lição do processo.

Tags: escalabilidade, paralelismo, programação, python

Comentar

Você precisa ser um usuário de mundo.IT para adicionar comentários!

Join mundo.IT

Humberto Massa Comentário por Humberto Massa em 29 outubro 2008 às 10:32
Eita. Esse é o tipo de pensamento que eu sempre admirei em você, Carlos: pragmático e prático. Saudades do tempo da PS.

© 2010   Criado por Yuri Gitahy

Badges  |  Relatar um incidente  |  Privacidade  |  Termos de serviço

Entrar no bate-papo