A otimização de software é um dos tópicos mais mal-entendidos pelos profissionais de TI. Muitos pensam que a performace só deve ser levada em consideração ao final do desenvolvimento, devido a uma máxima conhecida por muitos "premature optimization is the root of all evil", em tradução livre "otimização prematura é a raiz de todos os males". Este dizer é mal-interpretado por muitos como: todas as considerações de performace devem ser feitas apenas ao final do desenvolvimento. É mesmo? Problemas de performance podem e devem ser previstos durante a fase de projeto, como tudo em software, quando mais cedo o problema é encontrado, mais barato é a solução.
O mal entendido
O dizer “otimização prematura é a raiz de todos os males” de Donald Knuth está incompleto. A forma completa é “We should forget about small efficiencies, say about 97% of the time, premature optimization is the root of all evil.”, em tradução livre “Devemos esquecer as pequenas eficiências, digamos 97% do tempo, otimização prematura é a raiz de todos os males.” Donald Knuth estava falando da micro-otimização do código, não fornecendo um passe livre para métodos ineficientes.
Outro ponto a ser considerado, é a época na qual Donald Knuth fez a sua declaração. Naqueles idos tempos, otimização normalmente consistia em ficar contando ciclos de CPU gastos em cada instrução. Com certeza, esse tipo de preocupação não é adequada no momento do projeto do sistema. Ele não estava falando da escolha do algoritmo, estruturas de dados, uso adequado de loops, etc..
O que deve ser feito?
O código fonte de um programa deve ser claro e de fácil manutenção. Pequenas ineficiências são perfeitamente aceitáveis. Em uma série de excelentes artigos de Thomas Aylesworth, Software Efficiency and Optimization Partes 1, 2 e 3, comenta sobre o caso do laço foreach, mais ineficiente porém mais claro que o laço for. A diferença é pequena o suficiente para não ter importância na maior parte do tempo no desenvolvimento de jogos e os cálculos realizados em jogos precisam ser rápidos o suficiente para que ele flua bem.
Um dos itens mais importantes para a velocidade de um programa é o algoritmo utilizado. Quando se faz a escolha de um determinado algoritmo deve-se poder estimar o seu comportamento, principalmente se o número de dados é alto. Esse comportamento normalmente é definido usando a notação Big O, a qual é usada para comparar algoritmos.
| Eficiência | Propriedade Matemática | Comentário |
| O(1) | Constante | Independe do tamanho da entrada. Eficiência ideal. Ex: pesquisa em uma tabela hash bem feita |
| O(log n) | Logarítmica | Cresce algoritmicamente com o tamanho da entrada. Excelente eficiência, pois o logaritmo cresce lentamente. Ex: pesquisa em uma árvore binária balanceada |
| O(n) | Linear | Cresce linearmente com o tamanho da entrada. Boa eficiência. Ex: pesquisa em uma lista encadeada |
| O(nlogn) | Não Linear | Desempenho semelhante ao linear, pois o logaritmo cresce lentamente. Desempenho aceitável para muitos dados. Ex: ordenação usando o método quicksort |
| O(n²) | Quadrática | Cresce quadradicamente com o tamanho da entrada. Problemas ocorrem com um volume grande de dados. Ex: ordenação usando o método bolha |
Um algoritimo O(n²) pode ser mais rápido para um número pequenos de dados que um algoritmo O(nlogn), mas com certeza à medida que este número aumenta, o tempo de execução do algoritmo menos eficiente tenderá a ser maior. Isso não quer dizer que o algoritmo mais eficiente deva sempre ser escolhido, muitas vezes um mais simples pode ser o mais adequado. Como escolher? A experiência ajuda, se houver dúvidas pode-se sempre testar com um prototipo.
Outro fato importante é saber as boas práticas da plataforma que você usa. Em Java, por exemplo, você não deve concatenar strings dentro de um loop usando a classe String, deve-se usar a classe StringBuffer. Sobre SQL, encontrei dois posts excelentes: um sobre loops e outro sobre Ad-hoc queries. O post sobre loops traz uma observação simples, aplicável um muitas situações: coloque dentro de loops apenas o que for necessário.
O que eu já vi por aí? Loops esperando um evento ocorrer sem um sleep (CPU vai a 100%); mensagens de log para debug, dentro de um loop, no código de produção causando lentidão (não havia como desabilitar); criar uma conecção SQL nova para cada usuário logado no sistema de controle de horas e só fechar quando houver logout ou a seção expirar (a empresa tinha direito a 3 conexões e mais de 100 usuários no sistema, na hora da saída, era uma beleza) . Eu já tive problema com o envio de um número excessivo de objetos para um Applet.
É impossível prever tudo. Fez o melhor dentro do seu conhecimento e o desempenho ainda é insatisfatório? Procure a causa e trate, provavelmente será em uma porção pequena do sistema.
Resumindo: “otimização prematura é a raiz de todos os males” não deve ser usada para justificar desperdício de recursos do usuário, nem para codificação descuidada. Deixe a micro-otimização apenas para quando for necessário, mas pense sim, ainda no projeto, no desempenho do sistema. Muitos softwares atuais são pesados, lentos, além do necessário. Bom desempenho e leveza vende bem.




![Verifique o meu feed [Valid RSS]](/images/valid-rss.png)
agosto 14th, 2009 at 7:13
Olá. Muito bom o artigo. Confesso que eu já fui adepto da otmização excessiva, que ficava pensando qual tipo de varável usar para ocupar menos memória, olhar o código de máquina gerado pra ver qual código era mais eficiente e etc…felizmente foi uma fase. Hoje eu acredito mais no bom senso e em boas práticas de programação, que eu acho que qualquer programador razoável consegue com a experiência.
Obrigado por passar no meu blog.
Abs.