Posso substituir código otimizado por código legível?

78

Às vezes, você se depara com uma situação em que precisa estender / melhorar algum código existente. Você vê que o código antigo é muito enxuto, mas também é difícil de estender e leva tempo para ler.

É uma boa idéia substituí-lo por código moderno?

Há algum tempo, gostei da abordagem lean, mas agora parece-me que é melhor sacrificar muitas otimizações em favor de abstrações mais altas, interfaces melhores e código mais legível e extensível.

Os compiladores parecem estar melhorando também, então coisas como struct abc = {}são silenciosamente transformadas em memsets, shared_ptrs estão praticamente produzindo o mesmo código que o ponteiro bruto, os modelos estão funcionando super bem porque produzem código super enxuto e assim por diante.

Mas, ainda assim, às vezes você vê matrizes baseadas em pilha e funções C antigas com alguma lógica obscura, e geralmente elas não estão no caminho crítico.

É uma boa ideia alterar esse código se você precisar tocar em um pequeno pedaço dele de qualquer maneira?

Coder
fonte
20
Legibilidade e otimizações não se opõem na maioria das vezes.
deadalnix
23
A legibilidade pode melhorar com alguns comentários?
YetAnotherUser
17
É preocupante que a OOP-ificação seja considerada 'código moderno' #
James James
7
como a filosofia slackware: se não está quebrado, não corrigi-lo, a menos que você tem um muito, muito boa razão para fazê-lo
osdamv
5
Por código otimizado, você quer dizer código otimizado real ou o chamado código otimizado?
dan04

Respostas:

115

Onde?

  • Em uma página inicial de um site em escala do Google, não é aceitável. Mantenha as coisas o mais rápido possível.

  • Em uma parte de um aplicativo usado por uma pessoa uma vez por ano, é perfeitamente aceitável sacrificar o desempenho para obter a legibilidade do código.

Em geral, quais são os requisitos não funcionais para a parte do código em que você está trabalhando? Se uma ação deve executar abaixo de 900 ms. em um determinado contexto (máquina, carga, etc.) 80% do tempo e, na verdade, ele executa menos de 200 ms. Em 100% do tempo, com certeza, torne o código mais legível, mesmo que possa impactar levemente o desempenho. Se, por outro lado, a mesma ação nunca foi executada em menos de dez segundos, bem, você deve tentar ver o que há de errado com o desempenho (ou o requisito em primeiro lugar).

Além disso, como a melhoria da legibilidade diminuirá o desempenho? Muitas vezes, os desenvolvedores estão adaptando o comportamento próximo à otimização prematura: eles têm medo de aumentar a legibilidade, acreditando que isso destruirá drasticamente o desempenho, enquanto o código mais legível passará alguns microssegundos mais fazendo a mesma ação.

MainMa
fonte
47
+1! Se você não tiver números, obtenha alguns números. Se você não tem tempo para obter números, não tem tempo para alterá-lo.
Tacroy
49
Frequentemente, os desenvolvedores estão "otimizando" com base em mitos e mal-entendidos, por exemplo, assumindo que "C" é mais rápido que "C ++" e evitando recursos de C ++ devido à sensação geral de que as coisas são mais rápidas sem números para fazer backup. Lembra-me de um desenvolvedor de C que eu segui que achou gotomais rápido do que para loops. Ironicamente, o otimizador se saiu melhor com loops, tornando o código mais lento e mais difícil de ler.
Gort the Robot
6
Em vez de adicionar outra resposta, marquei esta resposta com +1. Se a compreensão dos fragmentos de código é tão importante, comente-os bem. Trabalhei em um ambiente C / C ++ / Assembly com código legado de uma década com dezenas de colaboradores. Se o código funcionar, deixe-o em paz e volte ao trabalho.
Chris K
É por isso que costumo escrever apenas código legível. O desempenho pode ser alcançado cortando os poucos pontos de acesso.
Luca
36

Geralmente não .

Alterar o código pode causar problemas imprevisíveis em outras partes do sistema (que às vezes podem passar despercebidos até muito mais tarde em um projeto, se você não tiver testes sólidos de unidade e fumaça no local). Eu costumo pensar "se não está quebrado, não conserte" a mentalidade.

A exceção a essa regra é se você estiver implementando um novo recurso que toque nesse código. Se, nesse ponto, não fizer sentido e a refatoração realmente precisar ocorrer, aguarde enquanto o tempo de refatoração (e testes e buffer suficientes para lidar com problemas indiretos) forem contabilizados em estimativas.

Obviamente, perfil, perfil, perfil , especialmente se for uma área de caminho crítico.

Demian Brecht
fonte
2
Sim, mas você supõe que a otimização era necessária. Nem sempre sabemos se foi e provavelmente queremos determinar isso primeiro.
haylem
2
@haylem: Não, presumo que o código funcione como está. Suponho também que refatorar o código invariavelmente causará problemas indiretos em outras partes do sistema (a menos que você esteja lidando com um pedaço trivial de código que não possui nenhuma dependência externa).
precisa
Há alguma verdade nesta resposta, e ironicamente, isso ocorre porque os problemas indiretos raramente são documentados, compreendidos, comunicados ou mesmo prestados atenção pelos desenvolvedores. Se os desenvolvedores tiverem uma compreensão mais profunda dos problemas que ocorreram no passado, eles saberão o que medir e ficarão mais confiantes em fazer alterações no código.
Rwong 23/10/2013
29

Em resumo: depende

  • Você realmente vai precisar ou usar sua versão refatorada / aprimorada?

    • Existe um ganho concreto, imediato ou a longo prazo?
    • Esse ganho é apenas para manutenção ou é realmente arquitetônico?
  • Realmente precisa ser otimizado?

    • Por quê?
    • Que ganho alvo você precisa atingir?

Em detalhes

Você vai precisar das coisas limpas e brilhantes?

Há coisas a serem cautelosas aqui, e você precisa identificar o limite entre o que é ganho real e mensurável e o que é apenas sua preferência pessoal e o mau hábito potencial de tocar código que não deveria ser.

Mais especificamente, saiba disso:

Existe o excesso de engenharia

É um anti-padrão e vem com problemas embutidos:

  • ele pode ser mais extensível , mas não pode ser mais fácil para estender,
  • ele pode não ser simples de entender ,
  • por último, mas definitivamente não menos importante aqui: você pode desacelerar todo o código.

Alguns também podem mencionar o princípio do KISS como referência, mas aqui é contra-intuitivo: a maneira otimizada é a maneira simples ou a maneira limpa da arquitetura? A resposta não é necessariamente absoluta, como explicado no restante abaixo.

Você não vai precisar

O princípio YAGNI não é completamente ortogonal com a outra questão, mas ajuda a se perguntar: você vai precisar?

A arquitetura mais complexa realmente apresenta um benefício para você, além de parecer mais sustentável?

Se não está quebrado, não conserte

Escreva isso em um cartaz grande e pendure-o próximo à tela, na área da cozinha no trabalho ou na sala de reuniões do desenvolvedor. É claro que existem muitos outros mantras que valem a pena se repetir, mas esse em particular é importante sempre que você tenta fazer um "trabalho de manutenção" e sente vontade de "melhorá-lo".

É natural que desejemos "melhorar" o código ou até mesmo tocá-lo, mesmo inconscientemente, enquanto lemos através dele para tentar entendê-lo. É uma coisa boa, pois significa que somos opinativos e tentamos entender melhor os internos, mas também está ligado ao nosso nível de habilidade, nosso conhecimento (como você decide o que é melhor ou não? Bem, veja as seções abaixo ...) e todas as suposições que fazemos sobre o que achamos que conhecemos o software ...:

  • realmente faz,
  • realmente precisa fazer,
  • acabará precisando fazer,
  • e quão bem ele faz isso.

Realmente precisa ser otimizado?

Tudo isso dito, por que foi "otimizado" em primeiro lugar? Eles dizem que a otimização prematura é a raiz de todos os males, e se você vir um código não documentado e aparentemente otimizado, normalmente você poderia assumir que provavelmente não seguiu as Regras de Otimização , não precisava muito do esforço de otimização e que era apenas o a arrogância do desenvolvedor habitual está entrando em ação. Mais uma vez, talvez seja apenas a sua falando agora.

Em caso afirmativo, dentro de quais limites se torna aceitável? Se for necessário, esse limite existe e oferece espaço para melhorar as coisas, ou uma linha dura para decidir deixá-lo ir.

Além disso, cuidado com as características invisíveis. Provavelmente, sua versão "extensível" desse código também aumentará a memória em tempo de execução e apresentará ainda mais espaço na memória estática para o executável. Os recursos brilhantes de OO vêm com custos não intuitivos como esses e podem ser importantes para o seu programa e o ambiente em que ele deve rodar.

Medida, Medida, Medida

Como o pessoal do Google agora, é tudo sobre dados! Se você pode fazer backup com dados, é necessário.

Existe uma história não tão antiga que, para cada US $ 1 gasto em desenvolvimento, será seguido por pelo menos US $ 1 em testes e pelo menos US $ 1 em suporte (mas, na verdade, é muito mais).

A mudança afeta muitas coisas:

  • pode ser necessário produzir uma nova compilação;
  • você deve escrever novos testes de unidade (definitivamente, se não houver, e sua arquitetura mais extensível provavelmente deixa espaço para mais, pois você tem mais superfície para bugs);
  • você deve escrever novos testes de desempenho (para garantir que isso permaneça estável no futuro e ver onde estão os gargalos), e isso é complicado de fazer ;
  • você precisará documentá-lo (e mais extensível significa mais espaço para detalhes);
  • você (ou outra pessoa) precisará testá-lo extensivamente no controle de qualidade;
  • o código (quase) nunca está livre de erros e você precisará apoiá-lo.

Portanto, não é apenas o consumo de recursos de hardware (velocidade de execução ou consumo de memória) que você precisa medir aqui, mas também o consumo de recursos da equipe . Ambos precisam ser previstos para definir um objetivo-alvo, para serem medidos, contabilizados e adaptados com base no desenvolvimento.

E para o seu gerente, isso significa ajustá-lo ao atual plano de desenvolvimento, por isso, comunique-o e não entre na codificação furiosa de cowboys / submarinos / black-ops.


Em geral...

Sim mas...

Não me interpretem mal, em geral, eu seria a favor de fazer o que você sugere, e eu frequentemente defendo isso. Mas você precisa estar ciente do custo a longo prazo.

Em um mundo perfeito, é a solução certa:

  • o hardware do computador melhora com o tempo,
  • compiladores e plataformas de tempo de execução melhoram com o tempo,
  • você obtém um código quase perfeito, limpo, sustentável e legível.

Na prática:

  • você pode piorar

    Você precisa de mais olhos para vê-lo, e quanto mais o complexificar, mais você precisará.

  • você não pode prever o futuro

    Você não pode saber com absoluta certeza se precisará disso, e nem mesmo se as "extensões" necessárias serão mais fáceis e rápidas de implementar na forma antiga, e se elas precisarão ser super otimizadas .

  • representa, do ponto de vista da administração, um enorme custo sem ganho direto.

Faça parte do processo

Você mencionou aqui que é uma mudança bastante pequena e tem alguns problemas específicos em mente. Eu diria que geralmente está bom neste caso, mas a maioria de nós também tem histórias pessoais de pequenas mudanças, edições quase cirúrgicas, que acabaram se transformando em pesadelo de manutenção e em prazos quase perdidos ou explodidos, porque Joe Programmer não viu um das razões por trás do código e tocou em algo que não deveria ter sido.

Se você tiver um processo para lidar com essas decisões, tire vantagem delas:

  • Se você testar as coisas corretamente, saberá mais rapidamente se as coisas estão quebradas,
  • Se você medi-los, saberá se eles melhoraram,
  • Se você o revisar, saberá se isso afasta as pessoas.

Cobertura de teste, criação de perfil e coleta de dados são complicados

Mas, é claro, seu código e métricas de teste podem sofrer os mesmos problemas que você está tentando evitar no seu código real: você testa as coisas certas, e elas são a coisa certa para o futuro, e você mede as coisas certas coisas?

Ainda assim, em geral, quanto mais você testar (até um certo limite) e medir, mais dados você coletará e mais seguro estará. Mau tempo de analogia: pense nisso como dirigir (ou a vida em geral): você pode ser o melhor motorista do mundo, se o carro avariar ou se alguém decidir se matar dirigindo seu carro hoje, o seu habilidades podem não ser suficientes. Existem duas coisas ambientais que podem afetar você, e os erros humanos também são importantes.

As revisões de código são os testes de corredor da equipe de desenvolvimento

E acho que a última parte é fundamental aqui: faça revisões de código. Você não saberá o valor de suas melhorias se as fizer sozinho. As revisões de código são o nosso "teste de corredor": siga a versão de Raymond da Lei de Linus para detectar bugs e detectar excesso de engenharia e outros antipadrões, e para garantir que o código esteja alinhado com as habilidades de sua equipe. Não faz sentido ter o "melhor" código se ninguém mais puder entender e mantê-lo, e isso vale tanto para otimizações enigmáticas quanto para projetos arquitetônicos profundos de 6 camadas.

Como palavras finais, lembre-se:

Todo mundo sabe que a depuração é duas vezes mais difícil do que escrever um programa. Então, se você é o mais esperto possível quando o escreve, como o depurará? - Brian Kernighan

haylem
fonte
"Se não está quebrado, não conserte" vai contra a refatoração. Não importa se algo funciona, se é impossível de manter, precisa ser alterado.
Miyamoto Akira
@MiyamotoAkira: é uma coisa de duas velocidades. Se não estiver quebrado, mas aceitável e com menor probabilidade de receber suporte, pode ser aceitável deixá-lo em paz, em vez de introduzir novos bugs em potencial ou gastar tempo de desenvolvimento nele. Trata-se de avaliar o benefício, a curto e longo prazo, da refatoração. Não há uma resposta clara, requer alguma avaliação.
585 haylem
acordado. Suponho que não goste da frase (e da filosofia por trás dela) porque vejo a refatoração como a opção padrão, e somente se parecer que isso vai demorar muito, ou é muito difícil, será / deve ser decidido não vai com isso. Lembre-se, eu fui queimado por pessoas que não mudaram as coisas, que apesar de funcionarem, eram claramente a solução errada assim que você teve que mantê-las ou ampliá-las.
Miyamoto Akira
@MiyamotoAkira: frases curtas e declarações de opinião não podem expressar muito. Eles devem estar na sua cara e ser desenvolvidos ao lado, eu acho. Eu mesmo sou muito importante em revisar e tocar em códigos o mais rápido possível, mesmo que muitas vezes sem uma grande rede de segurança ou sem muitas razões. Se estiver sujo, limpe-o. Mas, da mesma forma, eu também me queimei algumas vezes. E ainda vai se queimar. Desde que não sejam de 3º grau, não me importo muito, até agora sempre houve queimaduras de curto prazo para ganhos de longo prazo.
haylem
8

Em geral, você deve se concentrar na legibilidade primeiro e no desempenho muito mais tarde. Na maioria das vezes, essas otimizações de desempenho são insignificantes, mas o custo de manutenção pode ser enorme.

Certamente todas as "pequenas" coisas devem ser alteradas em favor da clareza, pois, como você apontou, a maioria delas será otimizada pelo compilador de qualquer maneira.

Quanto às otimizações maiores, pode haver uma chance de que as otimizações sejam realmente críticas para alcançar um desempenho razoável (embora esse não seja o caso surpreendentemente frequente). Eu faria suas alterações e depois traçaria o perfil do código antes e depois das alterações. Se o novo código tiver problemas significativos de desempenho, você sempre poderá reverter para a versão otimizada e, caso contrário, poderá continuar com a versão do código mais limpo.

Altere apenas uma parte do código de cada vez e veja como isso afeta o desempenho após cada rodada de refatoração.

Oleksi
fonte
8

Depende de por que o código foi otimizado e qual seria o efeito de alterá-lo e qual poderia ser o impacto do código no desempenho geral. Também deve depender se você tem uma boa maneira de carregar as alterações de teste.

Você não deve fazer essa alteração sem criar um perfil antes e depois e de forma perferível sob uma carga semelhante à que seria vista na produção. Isso significa não usar um pequeno subconjunto de dados em uma máquina do desenvolvedor ou testar quando apenas um usuário está usando o sistema.

Se a otimização foi recente, você poderá conversar com o desenvolvedor e descobrir exatamente qual era o problema e quão lento o aplicativo era antes da otimização. Isso pode lhe dizer muito se vale a pena fazer a otimização e para quais condições a otimização era necessária (um relatório que cobre um ano inteiro, por exemplo, pode não ter ficado lento até setembro ou outubro, se você estiver testando sua alteração em fevereiro, a lentidão talvez ainda não seja aparente e o teste é inválido).

Se a otimização for bastante antiga, os métodos mais novos podem ser mais rápidos e mais legíveis.

Em última análise, esta é uma pergunta para o seu chefe. É demorado refatorar algo que foi otimizado e garantir que a alteração não afete o resultado final e que ela tenha um desempenho tão bom ou pelo menos aceitável em comparação com a maneira antiga. Ele pode querer que você gaste seu tempo em outras áreas, em vez de assumir uma tarefa de alto risco para economizar alguns minutos de tempo de codificação. Ou ele pode concordar que o código é difícil de entender e precisa de intervenção frequente e que melhores métodos estão agora disponíveis.

HLGEM
fonte
6

se a criação de perfil mostrar que a otimização não é necessária (não está em uma seção crítica) ou ainda tem um tempo de execução pior (como resultado de uma otimização prematura ruim), substitua-o pelo código legível que é mais fácil de manter

verifique também se o código se comporta da mesma maneira com os testes apropriados

2 rotações
fonte
5

Pense nisso da perspectiva dos negócios. Quais são os custos da mudança? Quanto tempo você precisa para fazer a alteração e quanto economizará a longo prazo, tornando o código mais fácil de estender ou manter? Agora anexe um preço a esse tempo e compare-o ao dinheiro perdido, reduzindo o desempenho. Talvez você precise adicionar ou atualizar um servidor para compensar o desempenho perdido. Talvez o produto não atenda mais aos requisitos e não possa mais ser vendido. Talvez não haja perda. Talvez a mudança aumente a robustez e economize tempo em outros lugares. Agora tome sua decisão.

Em uma nota lateral, em alguns casos, pode ser possível manter as duas versões de um fragmento. Você pode escrever um teste gerando valores de entrada aleatórios e verificar os resultados com a outra versão. Use a solução "inteligente" para verificar o resultado de uma solução perfeitamente compreensível e obviamente correta e, assim, obtenha alguma garantia (mas nenhuma prova) de que a nova solução é equivalente à antiga. Ou faça o contrário e verifique o resultado do código complicado com o código detalhado e, assim, documentando a intenção por trás do hack de maneira inequívoca.

scarfridge
fonte
4

Basicamente, você está perguntando se a refatoração é um empreendimento que vale a pena. A resposta para isso é certamente sim.

Mas...

... você precisa fazer isso com cuidado. Você precisa de testes sólidos de unidade, integração, funcionalidade e desempenho para qualquer código que esteja refatorando. Você precisa ter certeza de que eles realmente testam todas as funcionalidades necessárias. Você precisa da capacidade de executá-los fácil e repetidamente. Depois disso, você poderá substituir componentes por novos componentes que contenham a funcionalidade equivalente.

Martin Fowler escreveu o livro sobre isso.

Matthew Flynn
fonte
3

Você não deve alterar o código de produção e de trabalho sem uma boa razão. A "refatoração" não é uma razão suficientemente boa, a menos que você não possa fazer seu trabalho sem essa refatoração. Mesmo que você esteja corrigindo erros no próprio código difícil, reserve um tempo para entendê-lo e fazer a menor alteração possível. Se o código é difícil de entender, você não será capaz de entendê-lo completamente e, portanto, quaisquer alterações feitas terão efeitos colaterais imprevisíveis - erros, em outras palavras. Quanto maior a alteração, maior a probabilidade de causar problemas.

Haveria uma exceção a isso: se o código incompreensível tivesse um conjunto completo de testes de unidade, você poderia refatorá-lo. Como nunca vi ou ouvi falar de código incompreensível com testes de unidade completos, você primeiro escreve os testes de unidade, obtém o acordo das pessoas necessárias para que esses testes de unidade representem de fato o que o código deveria estar fazendo e, então, faz as alterações no código . Eu fiz isso uma ou duas vezes; é uma dor no pescoço e muito caro, mas produz bons resultados no final.

mjfgates
fonte
3

Se é apenas um pequeno pedaço de código que faz algo relativamente simples de uma maneira difícil de entender, eu mudaria o "entendimento rápido" em um comentário estendido e / ou em uma implementação alternativa não utilizada, como

#ifdef READABLE_ALT_IMPLEMENTATION

   double x=0;
   for(double n: summands)
     x += n;
   return x;

#else

   auto subsum = [&](int lb, int rb){
          double x=0;
          while(lb<rb)
            x += summands[lb++];
          return x;
        };
   double x_fin=0;
   for(double nsm: par_eval( subsum
                           , partitions(n_threads, 0, summands.size()) ) )
     x_fin += nsm;
   return x_fin;

#endif
leftaroundabout
fonte
3

A resposta é, sem perda de generalidade, sim. Sempre adicione código moderno quando achar difícil ler o código e exclua o código incorreto na maioria dos casos. Eu uso o seguinte processo:

  1. Procure o teste de desempenho e informações de perfil de suporte. Se não houver teste de desempenho, o que pode ser afirmado sem evidência pode ser descartado sem evidência. Afirme que seu código moderno é mais rápido e remova o código antigo. Se alguém argumentar (até você mesmo), peça que escreva o código de perfil para provar qual é o mais rápido.
  2. Se o código de criação de perfil existir, escreva o código moderno de qualquer maneira. Nomeie algo como <function>_clean(). Em seguida, "concorra" ao seu código contra o código incorreto. Se o seu código for melhor, remova o código antigo.
  3. Se o código antigo for mais rápido, deixe seu código moderno lá de qualquer maneira. Ele serve como uma boa documentação para o que o outro código deve fazer e, como o código de "corrida" existe, você pode continuar executando-o para documentar as características de desempenho e as diferenças entre os dois caminhos. Você também pode testar a unidade quanto a diferenças no comportamento do código. É importante ressaltar que o código moderno superará o código "otimizado" um dia, garantido. Você pode remover o código incorreto.

QED.

Sunny Kalsi
fonte
3

Se eu pudesse ensinar uma coisa ao mundo (sobre Software) antes de morrer, ensinaria que "Desempenho versus X" é um falso dilema.

A refatoração é tipicamente conhecida como um benefício pela legibilidade e confiabilidade, mas também pode ser facilmente suportada pela otimização. Ao lidar com a melhoria de desempenho como uma série de refatorações, você pode respeitar a regra do parque de campismo e também acelerar o aplicativo. Na verdade, é, pelo menos na minha opinião, incumbido eticamente de você fazê-lo.

Por exemplo, o autor desta pergunta encontrou um pedaço de código maluco. Se essa pessoa estivesse lendo meu código, descobriria que a parte maluca tem 3-4 linhas. Está em um método por si só, e o nome e a descrição do método indicam O QUE o método está fazendo. O método conteria de 2 a 6 linhas de comentários embutidos, descrevendo COMO o código maluco obtém a resposta certa, apesar de sua aparência questionável.

Compartimentado dessa maneira, você é livre para trocar as implementações desse método como desejar. De fato, foi provavelmente assim que escrevi a versão maluca para começar. Você pode tentar, ou pelo menos perguntar sobre alternativas. Na maioria das vezes, você descobrirá que a implementação ingênua é notavelmente pior (geralmente eu apenas me preocupo com uma melhoria de 2-10x), mas compiladores e bibliotecas estão sempre mudando e quem sabe o que você pode encontrar hoje que não estava disponível quando a função foi escrita?

Jason
fonte
Uma chave importante para a eficiência em muitos casos é fazer com que um código faça o máximo de trabalho possível de maneiras que possam ser feitas com eficiência. Uma das coisas que me irrita com o .NET é que não existem mecanismos eficientes para, por exemplo, copiar parte de uma coleção para outra. A maioria das coleções armazena grandes grupos de itens consecutivos (se não a coisa toda) em matrizes, portanto, por exemplo, copiar os últimos 5.000 itens de uma lista de 50.000 itens deve se decompor em algumas operações de cópia em massa (se não apenas uma) e mais algumas outras etapas realizadas no máximo algumas vezes cada.
Supercat
Infelizmente, mesmo nos casos em que seja possível executar essas operações com eficiência, muitas vezes será necessário que loops "volumosos" sejam executados por 5.000 iterações (e, em alguns casos, 45.000!). Se uma operação puder ser reduzida a coisas como cópias de matriz em massa, elas poderão ser otimizadas a graus extremos, gerando grandes recompensas. Se cada iteração de loop precisar executar uma dúzia de etapas, é difícil otimizar qualquer uma delas particularmente bem.
Supercat
2

Provavelmente não é uma boa ideia tocá-lo - se o código foi escrito dessa maneira por razões de desempenho, significa que alterá-lo pode trazer de volta problemas de desempenho que foram resolvidos anteriormente.

Se você não decidir mudar as coisas para ser mais legível e extensível: Antes de fazer uma mudança, de referência o código antigo sob pesada carga. Melhor ainda, se você puder encontrar um documento antigo ou um registro de problema descrevendo o problema de desempenho que esse código de aparência estranha deve corrigir. Depois de fazer as alterações, execute os testes de desempenho novamente. Se não for muito diferente, ou ainda dentro de parâmetros aceitáveis, provavelmente está OK.

Às vezes, pode acontecer que, quando outras partes de um sistema mudam, esse código com desempenho otimizado não precisa mais de otimizações tão pesadas, mas não há como saber disso sem testes rigorosos.

FrustratedWithFormsDesigner
fonte
1
Um dos caras com quem trabalho agora gosta de otimizar as coisas em áreas que os usuários acessam uma vez por mês, se isso com frequência. Leva tempo e, com pouca frequência, causa outros problemas, porque ele gosta de codificar e confirmar e permitir que o controle de qualidade ou outra função downstream efetivamente teste. : / Para ser justo, ele geralmente é rápido, rápido e preciso, mas essas "otimizações" centavos apenas dificultam as coisas para o resto da equipe e sua morte permanente seria uma coisa boa.
Davee
@DaveE: essas otimizações são aplicadas devido a problemas reais de desempenho? Ou esse desenvolvedor faz isso apenas porque pode? Eu acho que se você souber que as otimizações não terão impacto, poderá substituí-las com segurança por um código mais legível, mas eu confiaria apenas em alguém especialista no sistema para fazer isso.
FrustratedWithFormsDesigner
Eles terminaram porque ele pode. Na verdade, ele geralmente salva alguns ciclos, mas quando a interação do usuário com o elemento do programa leva algum número de segundos (15 a 300 ish), reduzir um décimo de segundo de tempo de execução em busca da "eficiência" é bobagem. Especialmente quando as pessoas que o seguem têm que levar tempo real para entender o que ele fez. Este é um aplicativo PowerBuilder originalmente construído há 16 anos, portanto, dada a gênese das coisas, a mentalidade talvez seja compreensível, mas ele se recusa a atualizar sua mentalidade para a realidade atual.
Davee
@ DaveE: Eu acho que concordo mais com o cara com quem você trabalha do que com você. Se eu não puder consertar coisas que são lentas por absolutamente nenhuma boa razão , ficarei louco. Se eu vir uma linha de C ++ que usa repetidamente o operador + para montar uma seqüência de caracteres, ou código que abre e lê / dev / urandom todas as vezes no loop apenas porque alguém se esqueceu de definir um sinalizador, então eu o corrijo. Por ser fanático por isso, consegui manter a velocidade, quando outras pessoas o deixaram deslizar um microssegundo por vez.
Zan Lynx
1
Bem, teremos que concordar em discordar. Passar uma hora mudando alguma coisa para economizar segundos fracionários em tempo de execução para uma função que é executada de vez em quando e deixando o código em forma de coçar a cabeça para os outros desenvolvedores não é ... certo. Se essas funções foram executadas repetidamente em partes de alto estresse do aplicativo, tudo bem. Mas não é esse o caso que estou descrevendo. Isso é realmente gratuito para a codificação de códigos por nenhuma outra razão senão dizer "Eu fiz isso que o UserX faz uma vez por semana um pouco mais rápido". Enquanto isso, temos um trabalho remunerado que precisa ser realizado.
Davee
2

O problema aqui é distinguir "otimizado" de legível e extensível, o que nós, como usuários, vemos como código otimizado e o que o compilador vê como otimizado, são duas coisas diferentes. O código que você está olhando para alterar pode não ser um gargalo e, portanto, mesmo que o código seja "enxuto", ele nem precisa ser "otimizado". Ou, se o código for antigo o suficiente, pode haver otimizações feitas pelo compilador para os integrados, o que torna o uso de uma estrutura interna simples mais nova igualmente ou mais eficiente que o código antigo.

E o código ilegível "enxuto" nem sempre é otimizado.

Eu costumava pensar que o código inteligente / lean era um código bom, mas às vezes aproveitando as regras obscuras da linguagem doía, em vez de ajudar na criação do código, fui mordido com mais frequência do que nunca em qualquer trabalho incorporado ao tentar seja inteligente porque o compilador transforma seu código inteligente em algo totalmente inutilizável pelo hardware incorporado.

Jeff Langemeier
fonte
2

Nunca substituirei o código otimizado pelo código legível, porque não posso comprometer o desempenho e optarei por usar os comentários adequados em cada seção, para que qualquer pessoa possa entender a lógica implementada nessa seção otimizada, que resolverá os dois problemas.

Portanto, o código será otimizado + o comentário apropriado também o tornará legível.

NOTA: Você pode tornar um Código Otimizado legível com a ajuda de comentários adequados, mas não pode tornar o Código Legível um Otimizado.

Logicalj
fonte
Eu ficaria cansado dessa abordagem, pois basta uma pessoa editar o código para esquecer de manter o comentário sincronizado. De repente cada revisão posterior vai ficar pensando que executa X, enquanto na verdade fazendo Y.
John D
2

Aqui está um exemplo para ver a diferença entre código simples e código otimizado: https://stackoverflow.com/a/11227902/1396264

no final da resposta, ele apenas substitui:

if (data[c] >= 128)
    sum += data[c];

com:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

Para ser justo, não tenho idéia do que a declaração if foi substituída, mas, como o respondente diz que são algumas operações bit a bit, dando o mesmo resultado (só vou acreditar na palavra dele) .

Isso é executado em menos de um quarto do tempo original (11,54 s vs 2,5 s)

vedant1811
fonte
1

A principal questão aqui é: a otimização é necessária?

Se for, não será possível substituí-lo por um código mais lento e legível. Você precisará adicionar comentários etc. para torná-lo mais legível.

Se o código não precisar ser otimizado, ele não deverá ser (a ponto de afetar a legibilidade) e você poderá fatorá-lo novamente para torná-lo mais legível.

NO ENTANTO - verifique se você sabe exatamente o que o código faz e como testá-lo antes de começar a mudar as coisas. Isso inclui o pico de uso, etc. Se não for necessário compor um conjunto de casos de teste e executá-los antes e depois, você não terá tempo para refatorar.

Stefan
fonte
1

É assim que eu faço as coisas: primeiro eu faço com que ele funcione em código legível, depois eu o otimizo. Eu mantenho a fonte original e documento minhas etapas de otimização.

Então, quando preciso adicionar um recurso, volto ao meu código legível, adiciono o recurso e sigo as etapas de otimização que documentei. Como você documentou, é muito rápido e fácil otimizar seu código com o novo recurso.

Pieter B
fonte
0

A legibilidade do IMHO é mais importante que o código otimizado, porque na maioria dos casos a micro-otimização não vale a pena.

Artigo sobre micro-otimizações sem sentido :

Como a maioria de nós, estou cansado de ler postagens de blog sobre micro-otimizações sem sentido, como substituir impressão por eco, ++ $ i por $ i ++ ou aspas duplas por aspas simples. Por quê? Porque 99.999999% do tempo, é irrelevante.

"print" usa mais um código de operação que "echo" porque realmente retorna algo. Podemos concluir que o eco é mais rápido que o impresso. Mas um código de operação não custa nada, realmente nada.

Eu tentei uma nova instalação do WordPress. O script é interrompido antes de terminar com um "Erro de barramento" no meu laptop, mas o número de códigos de operação já era superior a 2,3 milhões. Disse o suficiente.

webvitaly
fonte
0

A otimização é relativa. Por exemplo:

Considere uma classe com vários membros do BOOL:

// no nitpicking over BOOL vs bool allowed
class Pear {
 ...
 BOOL m_peeled;
 BOOL m_sliced;
 BOOL m_pitted;
 BOOL m_rotten;
 ...
};

Você pode ser tentado a converter os campos BOOL em campos de bits:

class Pear {
 ...
 BOOL m_peeled:1;
 BOOL m_sliced:1;
 BOOL m_pitted:1;
 BOOL m_rotten:1;
 ...
};

Como um BOOL é digitado como INT (que nas plataformas Windows é um número inteiro de 32 bits assinado), isso leva dezesseis bytes e os agrupa em um. Isso representa uma economia de 93%! Quem poderia reclamar disso?

Esta suposição:

Como um BOOL é digitado como INT (que nas plataformas Windows é um número inteiro de 32 bits assinado), isso leva dezesseis bytes e os agrupa em um. Isso representa uma economia de 93%! Quem poderia reclamar disso?

leva a:

Converter um BOOL em um campo de bit único economizou três bytes de dados, mas custa oito bytes de código quando o membro recebe um valor não constante. Da mesma forma, extrair o valor fica mais caro.

O que costumava ser

 push [ebx+01Ch]      ; m_sliced
 call _Something@4    ; Something(m_sliced);

torna-se

 mov  ecx, [ebx+01Ch] ; load bitfield value
 shl  ecx, 30         ; put bit at top
 sar  ecx, 31         ; move down and sign extend
 push ecx
 call _Something@4    ; Something(m_sliced);

A versão do campo de bits é maior em nove bytes.

Vamos sentar e fazer alguma aritmética. Suponha que cada um desses campos de campo de bits seja acessado seis vezes no seu código, três vezes para gravação e três vezes para leitura. O custo no crescimento do código é de aproximadamente 100 bytes. Não terá exatamente 102 bytes, porque o otimizador poderá tirar proveito dos valores já registrados nos registros de algumas operações, e as instruções adicionais podem ter custos ocultos em termos de flexibilidade reduzida dos registros. A diferença real pode ser mais, pode ser menor, mas, para um cálculo de retorno, vamos chamá-lo de 100. Enquanto isso, a economia de memória era de 15 bytes por classe. Portanto, o ponto de equilíbrio é sete. Se o seu programa criar menos de sete instâncias dessa classe, o custo do código excederá a economia de dados: Sua otimização de memória foi uma des otimização da memória.

Referências

Paul Sweatte
fonte