A otimização prematura é realmente a raiz de todo o mal?

198

Um colega meu cometeu hoje uma classe chamada ThreadLocalFormat , que basicamente moveu instâncias de classes de formato Java para um segmento local, já que elas não são seguras para o thread e "relativamente caras" para serem criadas. Eu escrevi um teste rápido e calculei que eu poderia criar 200.000 instâncias por segundo, perguntei a ele se ele estava criando tantas, às quais ele respondeu "nem de perto isso". Ele é um ótimo programador e todos na equipe são altamente qualificados, por isso não temos nenhum problema em entender o código resultante, mas foi claramente um caso de otimização onde não há necessidade real. Ele apoiou o código no meu pedido. O que você acha? Este é um caso de "otimização prematura" e quão ruim é realmente?

    
por Craig Day 29.12.2015 / 08:56
fonte

17 respostas

303

É importante ter em mente a citação completa:

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

O que isto significa é que, na ausência de problemas de desempenho medidos, você não deve otimizar porque acha que você obterá um ganho de desempenho. Existem otimizações óbvias (como não fazer concatenação de strings dentro de um loop fechado), mas qualquer coisa que não seja uma otimização trivialmente clara deve ser evitada até que possa ser medida.

Os maiores problemas com a "otimização prematura" são que ela pode introduzir erros inesperados e pode ser um grande desperdício de tempo.

    
por 11.12.2014 / 18:46
fonte
103

Otimizações prematuras micro são a raiz de todo o mal, porque as micro otimizações deixam de fora o contexto. Eles quase nunca se comportam da maneira esperada.

Quais são algumas boas otimizações iniciais na ordem de importância:

  • Otimizações arquitetônicas (estrutura do aplicativo, o modo como ele é dividido em camadas e em camadas)
  • Otimizações do fluxo de dados (dentro e fora do aplicativo)

Algumas otimizações no meio do ciclo de desenvolvimento:

  • Estruturas de dados, introduzem novas estruturas de dados com melhor desempenho ou menor sobrecarga, se necessário
  • Algoritmos (agora é um bom momento para começar a decidir entre quicksort3 e heapsort ;-))

Algumas otimizações do ciclo de desenvolvimento final

  • Encontrar hotpots de código (loops apertados, que devem ser otimizados)
  • Otimizações baseadas em criação de perfil de partes computacionais do código
  • Micro otimizações podem ser feitas agora, como são feitas no contexto do aplicativo, e seu impacto pode ser medido corretamente.

Nem todas as otimizações iniciais são más, as micro otimizações são más se feitas na hora errada no ciclo de vida do desenvolvimento , pois podem afetar negativamente a arquitetura, afetar negativamente a produtividade inicial e desempenho irrelevante sábio ou até mesmo ter um efeito prejudicial no final do desenvolvimento devido a diferentes condições ambientais.

Se o desempenho é preocupante (e sempre deve ser) sempre pense grande . O desempenho é uma imagem maior e não sobre coisas como: devo usar int ou longo ?. Vá para Top Down ao trabalhar com o desempenho em vez de Bottom Up .

    
por 06.10.2015 / 15:07
fonte
49

otimização sem primeiro medir é quase sempre prematura.

Acredito que isso seja verdade neste caso e verdadeiro no caso geral também.

    
por 17.10.2008 / 11:29
fonte
39

A otimização é "maligna" se causar:

  • código menos claro
  • significativamente mais código
  • código menos seguro
  • tempo de programador desperdiçado

No seu caso, parece que um pequeno programador já foi gasto, o código não era muito complexo (um palpite do seu comentário que todos da equipe poderiam entender), e o código é um pouco mais futuro prova (sendo thread seguro agora, se eu entendi sua descrição). Soa como apenas um pouco de mal. :)

    
por 17.10.2008 / 10:42
fonte
33

Surpreende-me que esta questão tenha 5 anos e, no entanto, ninguém tenha postado mais do que Knuth tinha a dizer do que algumas frases. O par de parágrafos que cercam a famosa citação explica isso muito bem. O artigo que está sendo citado é chamado de " Programação Estruturada com vá para Declarações ", e embora seja quase Com 40 anos, trata-se de uma controvérsia e um movimento de software que não existem mais, e tem exemplos em linguagens de programação que muitas pessoas nunca ouviram falar, uma quantidade surpreendentemente grande do que ele diz ainda se aplica.

Aqui está uma citação maior (da página 8 do pdf, página 268 no original):

The improvement in speed from Example 2 to Example 2a is only about 12%, and many people would pronounce that insignificant. The conventional wisdom shared by many of today's software engineers calls for ignoring efficiency in the small; but I believe this is simply an overreaction to the abuses they see being practiced by penny-wise-and-pound-foolish programmers, who can't debug or maintain their "optimized" programs. In established engineering disciplines a 12% improvement, easily obtained, is never considered marginal; and I believe the same viewpoint should prevail in software engineering. Of course I wouldn't bother making such optimizations on a one-shot job, but when it's a question of preparing quality programs, I don't want to restrict myself to tools that deny me such efficiencies.

There is no doubt that the grail of efficiency leads to abuse. Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified. It is often a mistake to make a priori judgments about what parts of a program are really critical, since the universal experience of programmers who have been using measurement tools has been that their intuitive guesses fail.

Outra boa parte da página anterior:

My own programming style has of course changed during the last decade, according to the trends of the times (e.g., I'm not quite so tricky anymore, and I use fewer go to's), but the major change in my style has been due to this inner loop phenomenon. I now look with an extremely jaundiced eye at every operation in a critical inner loop, seeking to modify my program and data structure (as in the change from Example 1 to Example 2) so that some of the operations can be eliminated. The reasons for this approach are that: a) it doesn't take long, since the inner loop is short; b) the payoff is real; and c) I can then afford to be less efficient in the other parts of my programs, which therefore are more readable and more easily written and debugged.

    
por 27.10.2013 / 04:20
fonte
16

Eu sempre vi essa citação usada para justificar código ou código obviamente inválido que, embora seu desempenho não tenha sido medido, provavelmente poderia ser feito com mais rapidez, sem aumentar o tamanho do código ou comprometer sua legibilidade.

Em geral, acho que as micro-otimizações iniciais podem ser uma má ideia. No entanto, as otimizações de macro (coisas como escolher um algoritmo O (log N) em vez de O (N ^ 2)) geralmente valem a pena e devem ser feitas cedo, pois pode ser um desperdício escrever um algoritmo O (N ^ 2) e em seguida, jogue-o fora completamente em favor de uma abordagem O (log N).

Note que as palavras podem ser : se o algoritmo O (N ^ 2) é simples e fácil de escrever, você pode jogá-lo fora mais tarde sem muita culpa se ele for muito lento. Mas se os dois algoritmos são similarmente complexos, ou se a carga de trabalho esperada é tão grande que você já sabe que precisará da mais rápida, a otimização antecipada é uma decisão de engenharia que reduzirá sua carga de trabalho a longo prazo.

Assim, em geral, acho que a abordagem correta é descobrir quais são suas opções antes de começar a escrever código e escolher conscientemente o melhor algoritmo para sua situação. Mais importante ainda, a frase "otimização prematura é a raiz de todo o mal" não é desculpa para a ignorância. Desenvolvedores de carreira devem ter uma ideia geral de quanto custo de operações comuns; eles devem saber, por exemplo,

  • que as sequências custam mais do que os números
  • que os idiomas dinâmicos são muito mais lentos que os idiomas com tipagem estática
  • as vantagens das listas vetoriais / vetoriais sobre listas vinculadas e vice-versa
  • quando usar um hashtable, quando usar um mapa ordenado e quando usar um heap
  • que (se funcionarem com dispositivos móveis) "double" e "int" têm desempenho semelhante em desktops (FP pode até ser mais rápido), mas "double" pode ser cem vezes mais lento em dispositivos móveis low-end sem FPUs;
  • que a transferência de dados pela Internet é mais lenta do que o HDD, os HDDs são muito mais lentos que a RAM, a RAM é muito mais lenta que a L1 e registra, e as operações da Internet podem bloquear indefinidamente (e falhar a qualquer momento).

E os desenvolvedores devem estar familiarizados com uma caixa de ferramentas de estruturas de dados e algoritmos para que possam usar facilmente as ferramentas certas para o trabalho.

Ter muito conhecimento e uma caixa de ferramentas pessoal permite que você otimize quase sem esforço. Colocar um grande esforço em uma otimização que pode ser desnecessária é mal (e eu admito cair nessa armadilha mais de uma vez). Mas quando a otimização é tão fácil quanto escolher um set / hashtable ao invés de um array, ou armazenar uma lista de números em double [] ao invés de string [], então por que não? Eu posso estar discordando de Knuth aqui, não tenho certeza, mas acho que ele estava falando sobre otimização de baixo nível enquanto eu estou falando sobre otimização de alto nível.

Lembre-se de que essa citação é originalmente de 1974. Em 1974, os computadores eram lentos e o poder de computação era caro, o que dava a alguns desenvolvedores uma tendência de super otimização, linha por linha. Eu acho que é isso que Knuth estava empurrando contra. Ele não estava dizendo "não se preocupe com a performance", porque em 1974 isso seria apenas uma conversa maluca. Knuth estava explicando como otimizar; Em suma, deve-se focar apenas nos gargalos e, antes de fazer isso, você deve realizar medições para descobrir os gargalos.

Observe que você não pode encontrar os gargalos até ter escrito um programa para medir, o que significa que algumas decisões de desempenho devem ser feitas antes que haja algo para medir. Às vezes, essas decisões são difíceis de mudar se você as errar. Por esse motivo, é bom ter uma ideia geral do que as coisas custam para que você possa tomar decisões razoáveis quando não houver dados concretos disponíveis.

Quão cedo para otimizar e quanto se preocupar com o desempenho dependem do trabalho. Ao escrever scripts que você só executará algumas vezes, preocupar-se com o desempenho é geralmente um completo desperdício de tempo. Mas se você trabalha para a Microsoft ou Oracle e está trabalhando em uma biblioteca que milhares de outros desenvolvedores usarão em milhares de maneiras diferentes, talvez seja melhor otimizar o trabalho, então que você pode cobrir todos os casos de uso diversificados de forma eficiente. Mesmo assim, a necessidade de desempenho deve sempre ser equilibrada com a necessidade de legibilidade, facilidade de manutenção, elegância, extensibilidade e assim por diante.

    
por 01.05.2012 / 23:58
fonte
13

Pessoalmente, conforme abordado em um tópico anterior , não uso Não acredito que a otimização inicial seja ruim em situações em que você sabe que vai atingir problemas de desempenho. Por exemplo, eu escrevo software de modelagem e análise de superfícies, onde lidei regularmente com dezenas de milhões de entidades. Planejar o desempenho ideal no estágio de projeto é muito superior à otimização tardia de um projeto fraco.

Outra coisa a considerar é como seu aplicativo será dimensionado no futuro. Se você considerar que seu código terá uma vida útil longa, otimizar o desempenho no estágio de design também é uma boa ideia.

Na minha experiência, a otimização tardia fornece recompensas escassas a um preço alto. Otimizando no estágio de design, através da seleção de algoritmos e ajustes, é muito melhor. Dependendo de um profiler para entender como o seu código funciona não é uma ótima maneira de obter código de alto desempenho, você deve saber isso de antemão.

    
por 23.05.2017 / 14:40
fonte
9

Na verdade, aprendi que a não-otimização prematura é mais frequentemente a raiz de todo o mal.

Quando as pessoas escrevem software, inicialmente terão problemas, como instabilidade, recursos limitados, má usabilidade e mau desempenho. Todos esses geralmente são consertados, quando o software amadurece.

Tudo isso, exceto o desempenho. Ninguém parece se importar com o desempenho. O motivo é simples: se um software falha, alguém conserta o bug e é isso, se um recurso estiver faltando, alguém o implementará e feito, se o software tiver desempenho ruim, em muitos casos não é devido a falta de micro-otimização, mas devido ao mau design e ninguém vai tocar o design do software. Sempre.

Olhe para Bochs. Está lento como o inferno. Será que vai ficar mais rápido? Talvez, mas apenas na faixa de alguns por cento. Nunca obterá desempenho comparável ao software de virtualização como o VMWare ou VBox ou até mesmo o QEMU. Porque é lento por design!

Se o problema de um software é que é lento, então porque é MUITO lento e isso só pode ser corrigido melhorando o desempenho de uma multidão. + 10% simplesmente não fará um software lento rápido. E você normalmente não receberá mais de 10% para otimizações posteriores.

Portanto, se o desempenho é QUALQUER importante para o seu software, você deve levar isso em consideração desde o início, ao projetá-lo, em vez de pensar "ah sim, é lento, mas podemos melhorar isso mais tarde". Porque você não pode!

Eu sei que isso não se encaixa no seu caso específico, mas responde à pergunta geral: "A otimização prematura é realmente a raiz de todo o mal?" - com um claro NÃO.

Toda otimização, como qualquer recurso, etc., deve ser projetada com cuidado e implementada com cuidado. E isso inclui uma avaliação adequada de custo e benefício. Não otimize um algoritmo para economizar alguns ciclos aqui e ali, quando ele não cria um ganho de desempenho mensurável.

Apenas como exemplo: você pode melhorar o desempenho de uma função inlining, possivelmente economizando um punhado de ciclos, mas ao mesmo tempo você provavelmente aumenta o tamanho do seu executável, aumentando as chances de erros de TLB e cache custarem milhares de ciclos ou até mesmo operações de paginação, que matarão o desempenho completamente. Se você não entende essas coisas, sua "otimização" pode acabar ruim.

A otimização estúpida é mais maligna do que a otimização "prematura", mas ambas ainda são melhores que a não-otimização prematura.

    
por 11.12.2014 / 19:08
fonte
6

Existem dois problemas com o PO: em primeiro lugar, o tempo de desenvolvimento usado para trabalho não essencial, que poderia ser usado para escrever mais recursos ou corrigir mais bugs, e em segundo lugar, a falsa sensação de segurança de que o código está sendo executado com eficiência. Frequentemente, o PO envolve a otimização do código que não será o gargalo da garrafa, sem perceber o código que o fará. O bit "prematuro" significa que a otimização é feita antes de um problema ser identificado usando medições adequadas.

Então, basicamente, sim, isso parece uma otimização prematura, mas eu não necessariamente desistiria disso, a menos que ela introduza bugs - afinal, ela foi otimizada agora (!)

    
por 17.10.2008 / 10:39
fonte
3

Eu acredito que é o que Mike Cohn chama de "gold-plating" no código - ou seja, gastar tempo em coisas que poderiam ser boas, mas não necessárias.

Ele desaconselhou.

P.S. O "revestimento de ouro" pode ser uma espécie de funcionalidade de sinos e assobios. Quando você olha para o código, ele toma a forma de otimização desnecessária, classes 'à prova de futuro', etc.

    
por 17.10.2008 / 10:42
fonte
3

Como não há problema em entender o código, esse caso pode ser considerado uma exceção.

Mas, em geral, a otimização leva a um código menos legível e menos compreensível e deve ser aplicado somente quando necessário. Um exemplo simples - se você sabe que precisa classificar apenas alguns elementos -, use o BubbleSort. Mas se você suspeitar que os elementos poderiam aumentar e você não sabe o quanto, então otimizar com o QuickSort (por exemplo) não é mal, mas uma obrigação. E isso deve ser considerado durante o projeto do programa.

    
por 17.10.2008 / 10:40
fonte
3

Descobri que o problema da otimização prematura ocorre principalmente quando a reescrita do código existente é mais rápida. Eu posso ver como poderia ser um problema escrever alguma otimização complicada em primeiro lugar, mas na maioria das vezes eu vejo a otimização prematura criando sua feia dificuldade em consertar o que não é (conhecido por ser) quebrado.

E o pior exemplo disso é sempre que vejo alguém reimplementando recursos de uma biblioteca padrão. Essa é uma grande bandeira vermelha. Como, uma vez vi alguém implementar rotinas personalizadas para manipulação de string porque ele estava preocupado que os comandos internos eram muito lentos.

Isso resulta em código que é mais difícil de entender (ruim) e queima muito tempo no trabalho que provavelmente não é útil (ruim).

    
por 29.05.2011 / 14:54
fonte
3

De uma perspectiva diferente, é minha experiência que a maioria dos programadores / desenvolvedores não planeja o sucesso e o "protótipo" quase sempre se torna o Release 1.0. Eu tenho experiência em primeira mão com 4 produtos originais separados em que o front-end elegante, sexy e altamente funcional (basicamente a interface do usuário) resultou em ampla adoção e entusiasmo do usuário. Em cada um desses produtos, os problemas de desempenho começaram a surgir dentro de períodos relativamente curtos (de 1 a 2 anos), especialmente quando clientes maiores e mais exigentes começaram a adotar o produto. Logo, o desempenho dominou a lista de problemas, embora o desenvolvimento de novos recursos tenha dominado a lista de prioridades da administração. Os clientes ficaram cada vez mais frustrados, pois cada lançamento adicionava novos recursos que pareciam ótimos, mas quase inacessíveis devido a problemas de desempenho.

Assim, falhas fundamentais de projeto e implementação que eram de pouca ou nenhuma preocupação no "protótipo" se tornaram grandes obstáculos para o sucesso de longo prazo dos produtos (e das empresas).

Sua demonstração do cliente pode ter excelente desempenho em seu laptop com DOMs XML, SQL Express e muitos dados em cache do lado do cliente. O sistema de produção provavelmente irá falhar se você for bem sucedido.

Em 1976 ainda estávamos debatendo as formas ótimas de calcular uma raiz quadrada ou ordenar uma grande matriz e o ditado de Don Knuth foi direcionado ao erro de se concentrar em otimizar esse tipo de rotina logo no início do processo de design resolvendo o problema e otimizando as regiões de código localizadas.

Quando se repete o ditado como uma desculpa para não escrever código eficiente (C ++, VB, T-SQL ou outro), ou por não projetar corretamente o armazenamento de dados, ou por não considerar a arquitetura de trabalho líquida, eles são IMO apenas demonstrando uma compreensão muito superficial da natureza real do nosso trabalho. Ray

    
por 11.12.2014 / 18:09
fonte
1

Suponho que isso depende de como você define "prematuro". Tornar a funcionalidade de baixo nível mais rápida quando você está escrevendo não é inerentemente mau. Eu acho que é um mal-entendido da citação. Às vezes, acho que essa citação poderia ter mais alguma qualificação. Eu ecoaria os comentários do m_pGladiator sobre a legibilidade embora.

    
por 17.10.2008 / 10:42
fonte
1

A resposta é: depende. Argumentarei que a eficiência é um grande negócio para certos tipos de trabalho, como consultas complexas a bancos de dados. Em muitos outros casos, o computador está gastando a maior parte do tempo aguardando a entrada do usuário, então otimizar a maioria dos códigos é, na melhor das hipóteses, um desperdício de esforço e, no pior, contraproducente.

Em alguns casos, você pode projetar para obter eficiência ou desempenho (percebido ou real) - selecionando um algoritmo apropriado ou projetando uma interface de usuário para que determinadas operações caras ocorram em segundo plano, por exemplo. Em muitos casos, o perfil ou outras operações para determinar os hotspots proporcionam um benefício de 10/90.

Um exemplo disso que eu posso descrever é o modelo de dados que eu fiz uma vez para um sistema de gerenciamento de processo judicial que tinha cerca de 560 tabelas nele. Começou normalizado ('belamente normalizado' como o consultor de uma certa firma de big-5 colocou) e nós só tivemos que colocar quatro itens de dados desnormalizados nele:

  • Uma visualização materializada para suportar uma tela de pesquisa

  • Uma tabela mantida pelo acionador para suportar outra tela de pesquisa que não pode ser feita com uma visão materializada.

  • Uma tabela de relatórios desnormalizada (isso só existia porque tínhamos que aceitar alguns relatórios de taxa de transferência quando um projeto de data warehouse era canalizado)

  • Uma tabela mantida pelo gatilho para uma interface que precisou procurar o mais recente de um grande número de eventos diferentes no sistema.

Este era (na época) o maior projeto de J2EE na Australásia - bem mais de 100 anos de tempo de desenvolvedor - e tinha 4 itens desordenados no esquema do banco de dados, um dos quais não pertencia realmente a nenhum.

    
por 01.05.2012 / 18:41
fonte
1

A otimização prematura não é a raiz de todo mal, com certeza. Existem no entanto desvantagens:

  • você investe mais tempo durante o desenvolvimento
  • você investe mais tempo testando
  • você investe mais tempo consertando bugs que de outra forma não estariam lá

Em vez de otimização prematura, pode-se fazer testes de visibilidade antecipada para ver se há uma necessidade real de melhor otimização.

    
por 11.12.2014 / 18:47
fonte
1

A maioria daqueles que aderem ao "PMO" (a citação parcial, isto é) dizem que as otimizações devem ser baseadas em medições e as medições não podem ser realizadas até o final.

Também é minha experiência em desenvolvimento de sistemas grandes que o teste de desempenho é feito no final, conforme o desenvolvimento se aproxima da conclusão.

Se fôssemos seguir o "conselho" dessas pessoas, todos os sistemas seriam terrivelmente lentos. Eles também seriam caros porque suas necessidades de hardware são muito maiores do que o originalmente previsto.

Há muito defendo fazer testes de desempenho em intervalos regulares no processo de desenvolvimento: ele indicará a presença de novo código (onde anteriormente não havia nenhum) e o estado do código existente.

  • O desempenho do código recém-implementado pode ser comparado com esse de código similar existente. Uma "sensação" para o desempenho do novo código será estabelecido ao longo do tempo.
  • Se o código existente de repente enlouquecer, você entende que algo aconteceu e você pode investigar imediatamente, não (muito) mais tarde, quando afeta todo o sistema.

Outra ideia de estimação é instrumentar o software no nível do bloco de funções. Conforme o sistema é executado, ele reúne informações sobre os tempos de execução dos blocos de funções. Quando uma atualização do sistema é executada, pode ser determinado o que os blocos de funções executam como no release anterior e aqueles que se deterioraram. Na tela de um software, os dados de desempenho podem ser acessados no menu de ajuda.

Confira esta peça excelente sobre o que o PMO pode ou não significar.

    
por 06.10.2015 / 15:15
fonte