Otimização de código: a maneira ideal de otimizar
Publicados: 2022-03-11A otimização de desempenho é uma das maiores ameaças ao seu código.
Você pode estar pensando, não mais uma dessas pessoas . Eu entendo. A otimização de qualquer tipo deve ser claramente uma coisa boa, a julgar por sua etimologia, então, naturalmente, você quer ser bom nisso.
Não apenas para se destacar da multidão como um desenvolvedor melhor. Não apenas para evitar ser “Dan” no The Daily WTF , mas porque você acredita que otimização de código é a coisa certa a fazer. Você se orgulha do seu trabalho.
O hardware do computador fica cada vez mais rápido e o software mais fácil de fazer, mas qualquer coisa simples que você só queira fazer, Dammit sempre leva mais tempo do que o anterior. Você balança a cabeça diante desse fenômeno (a propósito, conhecido como Lei de Wirth) e resolve contrariar essa tendência.
Isso é nobre da sua parte, mas pare.
Simplesmente pare!
Você corre o maior risco de frustrar seus próprios objetivos, não importa o quão experiente você seja em programação.
Como assim? Vamos voltar.
Em primeiro lugar, o que é otimização de código?
Muitas vezes, quando o definimos, assumimos que queremos que o código tenha um desempenho melhor. Dizemos que otimização de código é escrever ou reescrever código para que um programa use o mínimo de memória ou espaço em disco possível, minimize seu tempo de CPU ou largura de banda de rede ou faça o melhor uso de núcleos adicionais.
Na prática, às vezes adotamos outra definição: escrever menos código.
Mas o código preventivo que você está escrevendo com esse objetivo é ainda mais provável de se tornar um espinho no lado de alguém. De quem? A próxima pessoa azarada que precisa compreender seu código, que pode até ser você mesmo. E alguém inteligente e capaz, como você, pode evitar a auto-sabotagem: mantenha seus fins nobres, mas reavalie seus meios, apesar de parecerem inquestionavelmente intuitivos.
Portanto, otimização de código é um termo um pouco vago. Isso é antes mesmo de considerarmos algumas das outras maneiras de otimizar o código, que veremos a seguir.
Vamos começar ouvindo os conselhos dos sábios enquanto exploramos juntos as famosas regras de otimização de código de Jackson:
- Não faça isso.
- (Apenas para especialistas!) Não faça isso ainda .
1. Não faça isso: canalizando o perfeccionismo
Vou começar com um exemplo embaraçosamente extremo de uma época em que, muito tempo atrás, eu estava apenas começando a mergulhar no maravilhoso mundo do SQL, coma-o-bolo-e-coma-o-demais. O problema foi que eu pisei no bolo e não quis mais comê-lo porque estava molhado e começou a cheirar a pés.
Eu estava apenas começando a mergulhar no maravilhoso mundo do SQL, coma-o-bolo-e-coma-o-demais. O problema foi que eu pisei no bolo…
Esperar. Deixe-me sair desse acidente de carro de uma metáfora que acabei de fazer e explicar.
Eu estava fazendo pesquisa e desenvolvimento para um aplicativo de intranet, que esperava que um dia se tornasse um sistema de gerenciamento completamente integrado para a pequena empresa onde eu trabalhava. Ele rastrearia tudo para eles e, ao contrário do sistema atual, nunca perderia seus dados, porque seria apoiado por um RDBMS, não o arquivo simples caseiro que outro desenvolvedor havia usado. Eu queria projetar tudo o mais inteligente possível desde o início, porque eu tinha uma lousa em branco. Idéias para este sistema explodiam como fogos de artifício em minha mente, e comecei a projetar tabela - contatos e suas muitas variações contextuais para um CRM, módulos de contabilidade, estoque, compras, CMS e gerenciamento de projetos, que em breve eu estaria usando dogfood.
Que tudo parou, em termos de desenvolvimento e desempenho, por causa de... você adivinhou, otimização.
Vi que os objetos (representados como linhas de tabela) podem ter muitos relacionamentos diferentes entre si no mundo real e que podemos nos beneficiar do rastreamento desses relacionamentos: reteríamos mais informações e poderíamos eventualmente automatizar a análise de negócios em todo o lugar. Vendo isso como um problema de engenharia, fiz algo que parecia uma otimização da flexibilidade do sistema.
Neste ponto, é importante cuidar do seu rosto, porque não serei responsável se a palma da mão doer. Preparar? Eu criei duas tabelas: relationship
e uma que tinha uma referência de chave estrangeira, relationship_type
. relationship
poderia se referir a quaisquer duas linhas em qualquer lugar em todo o banco de dados e descrever a natureza do relacionamento entre elas.
Oh cara. Eu tinha acabado de otimizar tanto essa flexibilidade.
Demais, na verdade. Agora eu tinha um novo problema: um determinado relationship_type
naturalmente não faria sentido entre cada combinação de linhas. Embora possa fazer sentido que uma person
tenha um vínculo employed by
com uma company
, isso nunca poderia ser semanticamente equivalente ao relacionamento entre, digamos, dois document
.
OK, sem problemas. Vamos apenas adicionar duas colunas a relationship_type
, especificando em quais tabelas esse relacionamento pode ser aplicado. (Pontos de bônus aqui se você acha que eu pensei em normalizar isso movendo essas duas colunas para uma nova tabela referente a relationship_type.id
, para que os relacionamentos que pudessem se aplicar semanticamente a mais de um par de tabelas não tivessem os nomes das tabelas duplicados. Afinal, se eu precisasse mudar o nome de uma tabela e esquecesse de atualizá-la em todas as linhas aplicáveis, isso poderia criar um bug! Em retrospecto, pelo menos os bugs teriam fornecido comida para as aranhas que habitam meu crânio.)
Felizmente, fiquei inconsciente em uma tempestade de pistas antes de viajar muito longe por esse caminho. Quando acordei, percebi que tinha conseguido, mais ou menos, reimplementar as tabelas internas relacionadas à chave estrangeira do RDBMS em cima de si. Normalmente eu gosto de momentos que terminam comigo fazendo a proclamação arrogante de que “eu sou tão meta”, mas isso, infelizmente, não foi um deles. Esqueça a falha em escalar - o inchaço horrendo desse design tornou o back-end do meu aplicativo ainda simples, cujo banco de dados ainda não era preenchido com dados de teste, quase inutilizável.
Vamos voltar por um segundo e dar uma olhada em duas das muitas métricas em jogo aqui. Uma é a flexibilidade, que tinha sido meu objetivo declarado. Nesse caso, minha otimização, sendo de natureza arquitetônica, nem foi prematura:
(Vamos falar mais sobre isso em meu artigo recentemente publicado, Como Evitar a Maldição da Otimização Prematura.) Mesmo assim, minha solução falhou espetacularmente por ser flexível demais . A outra métrica, escalabilidade, era uma que eu nem estava considerando ainda, mas consegui destruir pelo menos tão espetacularmente com danos colaterais.
Isso mesmo, “Ah”.
Essa foi uma lição poderosa para mim de como a otimização pode dar errado. Meu perfeccionismo implodiu completamente: minha esperteza me levou a produzir uma das soluções mais objetivamente não inteligentes que já fiz.
Otimize seus hábitos, não seu código
À medida que você se pega tendendo a refatorar antes mesmo de ter um protótipo funcional e um conjunto de testes para evidenciar sua correção, considere onde mais você pode canalizar esse impulso. Sudoku e Mensa são ótimos, mas talvez algo que realmente beneficie seu projeto diretamente seja melhor:
- Segurança
- Estabilidade do tempo de execução
- Clareza e estilo
- Eficiência de codificação
- Eficácia do teste
- Perfil
- Seu kit de ferramentas/DE
- SECA (não se repita)
Mas cuidado: a otimização de qualquer um desses recursos custará aos outros. No mínimo, isso vem ao custo do tempo.
Aqui é onde é fácil ver o quanto de arte existe na criação de código. Para qualquer um dos itens acima, posso contar histórias sobre como muito ou pouco foi considerado a escolha errada. Quem está pensando aqui também é uma parte importante do contexto.
Por exemplo, em relação ao DRY: Em um trabalho que tive, herdei uma base de código que era pelo menos 80% de declarações redundantes, porque seu autor aparentemente não sabia como e quando escrever uma função. Os outros 20% do código eram confusamente auto-semelhantes.
Fui encarregado de adicionar alguns recursos a ele. Um desses recursos precisaria ser repetido em todo o código a ser implementado, e qualquer código futuro teria que ser cuidadosamente copiado para fazer uso do novo recurso.
Obviamente, precisava ser refatorado apenas para minha própria sanidade (alto valor) e para quaisquer futuros desenvolvedores. Mas, como eu era novo na base de código, primeiro escrevi testes para garantir que minha refatoração não introduzisse regressões. Na verdade, eles fizeram exatamente isso: eu peguei dois bugs ao longo do caminho que eu não teria notado entre toda a saída gobbledygook que o script produziu.
No final, achei que tinha me saído muito bem. Após a refatoração, impressionei meu chefe por ter implementado o que havia sido considerado um recurso difícil com algumas linhas simples de código; além disso, o código era, em geral, uma ordem de magnitude mais eficiente. Mas não demorou muito para que o mesmo chefe me dissesse que eu tinha sido muito lento e que o projeto já deveria ter terminado. Tradução: A eficiência de codificação era uma prioridade maior.
Cuidado: a otimização de qualquer [aspecto] em particular virá à custa de outros. No mínimo, isso vem ao custo do tempo.
Ainda acho que fiz o curso certo lá, mesmo que a otimização do código não tenha sido apreciada diretamente pelo meu chefe na época. Sem a refatoração e os testes, acho que levaria mais tempo para realmente ficar correto – ou seja, focar na velocidade de codificação teria realmente frustrado isso. (Ei, esse é o nosso tema!)
Compare isso com alguns trabalhos que fiz em um pequeno projeto paralelo meu. No projeto, eu estava tentando um novo mecanismo de modelo e queria adquirir bons hábitos desde o início, mesmo que experimentar o novo mecanismo de modelo não fosse o objetivo final do projeto.
Assim que notei que alguns blocos que adicionei eram muito semelhantes entre si e, além disso, cada bloco exigia que se referisse à mesma variável três vezes, o sino DRY tocou na minha cabeça e comecei a encontrar o maneira de fazer o que eu estava tentando fazer com este mecanismo de modelo.
Descobriu-se, após algumas horas de depuração infrutífera, que isso não era possível no momento com o mecanismo de modelo da maneira que eu imaginava. Não só não havia uma solução SECA perfeita ; não havia nenhuma solução DRY!
Tentando otimizar esse meu único valor, descarrilei completamente minha eficiência de codificação e minha felicidade, porque esse desvio custou ao meu projeto o progresso que eu poderia ter tido naquele dia.
Mesmo assim, eu estava totalmente errado? Às vezes, vale a pena investir um pouco, principalmente com um novo contexto tecnológico, para conhecer as melhores práticas mais cedo e não mais tarde. Menos código para reescrever e maus hábitos para desfazer, certo?
Não, acho que foi imprudente até mesmo procurar uma maneira de reduzir a repetição em meu código - em contraste com minha atitude na anedota anterior. A razão é que o contexto é tudo: eu estava explorando uma nova peça de tecnologia em um pequeno projeto de jogo, não me acomodando a longo prazo. Algumas linhas extras e repetições não teriam prejudicado ninguém, mas a perda de foco prejudicou a mim e ao meu projeto.
Espere, então buscar as melhores práticas pode ser um mau hábito? Às vezes. Se meu objetivo principal fosse aprender o novo mecanismo, ou aprender em geral, teria sido um tempo bem gasto: mexendo, encontrando os limites, descobrindo recursos não relacionados e pegadinhas por meio de pesquisa. Mas eu tinha esquecido que esse não era meu objetivo principal, e isso me custou.
É uma arte, como eu disse. E o desenvolvimento dessa arte se beneficia do lembrete, não faça isso . Pelo menos, leva você a considerar quais valores estão em jogo enquanto você trabalha e quais são mais importantes para você em seu contexto.
E essa segunda regra? Quando podemos realmente otimizar?
2. Não faça isso ainda : alguém já fez isso
OK, seja por você ou por outra pessoa, você descobre que sua arquitetura já foi definida, os fluxos de dados foram pensados e documentados e é hora de codificar.
Vamos dar um passo além: Não codifique ainda .
Isso em si pode cheirar a otimização prematura, mas é uma exceção importante. Por quê? Para evitar o temido NIHS, ou Síndrome “Não Inventado Aqui” – assumindo que suas prioridades incluem desempenho de código e minimização do tempo de desenvolvimento. Se não, se seus objetivos são completamente orientados para o aprendizado, você pode pular esta próxima seção.
Embora seja possível que as pessoas reinventem a roda do quadrado por pura arrogância, acredito que pessoas honestas e humildes, como você e eu, podem cometer esse erro apenas por não conhecer todas as opções disponíveis para nós. Conhecer todas as opções de cada API e ferramenta em sua pilha e mantê-las no topo à medida que crescem e evoluem é certamente muito trabalhoso.
Mas, colocar esse tempo é o que faz de você um especialista e evita que você seja a zilionésima pessoa no CodeSOD a ser amaldiçoada e ridicularizada pelo rastro de devastação deixado para trás por sua fascinante visão de calculadoras de data e hora ou manipuladores de strings.
(Um bom contraponto a esse padrão geral é a antiga Java Calendar
API, mas já foi corrigida.)
Verifique sua biblioteca padrão, verifique o ecossistema do seu framework, verifique se o FOSS já resolve seu problema
As chances são de que os conceitos com os quais você está lidando tenham nomes bastante comuns e conhecidos, portanto, uma pesquisa rápida na Internet economizará muito tempo.
Como exemplo, eu estava recentemente me preparando para fazer algumas análises de estratégias de IA para um jogo de tabuleiro. Acordei uma manhã percebendo que a análise que eu estava planejando poderia ser feita com muito mais eficiência se eu simplesmente usasse um certo conceito de combinatória que eu lembrava. Não estando interessado em descobrir o algoritmo para este conceito neste momento, eu já estava à frente sabendo o nome certo para procurar. No entanto, descobri que após cerca de 50 minutos de pesquisa e experimentando alguns códigos preliminares, não consegui transformar o pseudo-código semi-acabado que encontrei em uma implementação correta. (Você acredita que há uma postagem no blog onde o autor assume a saída incorreta do algoritmo, implementa o algoritmo incorretamente para corresponder às suposições, os comentaristas apontam isso e, anos depois, ainda não foi corrigido?) Nesse ponto, meu chá da manhã entrou em ação e procurei por [name of concept] [my programming language]
. 30 segundos depois, eu tinha o código comprovadamente correto do GitHub e estava passando para o que eu realmente queria fazer. Apenas ser específico e incluir a linguagem, em vez de assumir que eu mesmo teria que implementá-la, significava tudo.
Hora de projetar sua estrutura de dados e implementar seu algoritmo
…de novo, não jogue golfe de código. Priorize a correção e a clareza em projetos do mundo real.
OK, então você olhou, e não há nada que resolva seu problema embutido em sua cadeia de ferramentas, ou licenciado liberalmente na web. Você lança o seu próprio.
Sem problemas. O conselho é simples, nesta ordem:
- Projete-o para que seja simples de explicar a um programador iniciante.
- Escreva um teste que atenda às expectativas produzidas por esse projeto.
- Escreva seu código para que um programador novato possa facilmente obter o design dele.
Simples, mas talvez difícil de seguir. É aqui que os hábitos de codificação, cheiros de código, arte, artesanato e elegância entram em jogo. Obviamente, há um aspecto de engenharia no que você está fazendo neste momento, mas, novamente, não jogue golfe de código. Priorize a correção e a clareza em projetos do mundo real.
Se você gosta de vídeos, aqui está um de alguém seguindo as etapas acima, mais ou menos. Para os avessos ao vídeo, vou resumir: é um teste de codificação de algoritmo em uma entrevista de emprego no Google. O entrevistado primeiro projeta o algoritmo de uma maneira que seja fácil de se comunicar. Antes de escrever qualquer código, há exemplos da saída esperada por um design funcional. Então o código segue naturalmente.
Quanto aos testes em si, sei que, em alguns círculos, o desenvolvimento orientado a testes pode ser controverso. Acho que parte do motivo é que pode ser exagerado, perseguido religiosamente a ponto de sacrificar o tempo de desenvolvimento. (Mais uma vez, dando um tiro no pé tentando otimizar até mesmo uma variável muito desde o início.) Mesmo Kent Beck não leva o TDD a tal extremo, e ele inventou a programação extrema e escreveu o livro sobre TDD. Portanto, comece com algo simples para garantir que sua saída esteja correta. Afinal, você estaria fazendo isso manualmente após a codificação de qualquer maneira, certo? (Minhas desculpas se você é um programador tão rockstar que nem executa seu código depois de escrevê-lo. Nesse caso, talvez você considere deixar os futuros mantenedores do seu código com um teste apenas para saber que eles não quebre sua implementação incrível.) Então, em vez de fazer uma comparação visual manual, com um teste pronto, você já está deixando o computador fazer esse trabalho para você.
Durante o processo bastante mecânico de implementação de seus algoritmos e estruturas de dados, evite fazer otimizações linha por linha e nem pense em usar uma linguagem externa de nível inferior personalizada (Assembly se você estiver codificando em C, C se você está codificando em Perl, etc.) neste momento. A razão é simples: se o seu algoritmo for totalmente substituído - e você só descobrirá mais tarde no processo se isso é necessário -, seus esforços de otimização de baixo nível não terão efeito no final.
Exemplo de ECMAScript
No excelente site de revisão de código da comunidade exercism.io, encontrei recentemente um exercício que sugeria explicitamente tentar otimizar para eliminação de duplicação ou clareza. Eu otimizei para desduplicação, apenas para mostrar o quão ridículas as coisas podem ficar se você levar DRY – uma mentalidade de codificação benéfica, como mencionei acima – longe demais. Veja como ficou meu código:
const zeroPhrase = "No more"; const wallPhrase = " on the wall"; const standardizeNumber = number => { if (number === 0) { return zeroPhrase; } return '' + number; } const bottlePhrase = number => { const possibleS = (number === 1) ? '' : 's'; return standardizeNumber(number) + " bottle" + possibleS + " of beer"; } export default class Beer { static verse(number) { const nextNumber = (number === 0) ? 99 : (number - 1); const thisBottlePhrase = bottlePhrase(number); const nextBottlePhrase = bottlePhrase(nextNumber); let phrase = thisBottlePhrase + wallPhrase + ", " + thisBottlePhrase.toLowerCase() + ".\n"; if (number === 0) { phrase += "Go to the store and buy some more"; } else { const bottleReference = (number === 1) ? "it" : "one"; phrase += "Take " + bottleReference + " down and pass it around"; } return phrase + ", " + nextBottlePhrase.toLowerCase() + wallPhrase + ".\n"; } static sing(start = 99, end = 0) { return Array.from(Array(start - end + 1).keys()).map(offset => { return this.verse(start - offset); }).join('\n'); } }
Dificilmente qualquer duplicação de strings lá! Ao escrevê-lo dessa maneira, implementei manualmente uma forma de compactação de texto para a música da cerveja (mas apenas para a música da cerveja). Qual foi o benefício, exatamente? Bem, digamos que você queira cantar sobre beber cerveja em latas em vez de garrafas. Eu poderia fazer isso alterando uma única instância de bottle
para can
.
Agradável!
…certo?
Não, porque então todos os testes quebram. OK, isso é fácil de corrigir: vamos apenas fazer uma pesquisa e substituir a bottle
na especificação do teste de unidade. E isso é exatamente tão fácil de fazer quanto fazer isso com o próprio código em primeiro lugar e traz os mesmos riscos de quebrar as coisas sem querer.
Enquanto isso, minhas variáveis serão estranhamente nomeadas depois, com coisas como bottlePhrase
não tendo nada a ver com garrafas . A única maneira de evitar isso é ter previsto exatamente o tipo de mudança que seria feita e usado um termo mais genérico como vessel
ou container
no lugar de bottle
em meus nomes de variáveis.
A sabedoria de se preparar para o futuro dessa maneira é bastante questionável. Quais são as chances de você querer mudar alguma coisa? E se você fizer isso, o que você mudar funcionará tão convenientemente? No exemplo bottlePhrase
, e se você quiser localizar em um idioma que tenha mais de duas formas no plural? Isso mesmo, tempo de refatoração, e o código pode ficar ainda pior depois.
Mas quando seus requisitos mudam e você não está apenas tentando antecipá-los , talvez seja hora de refatorar. Ou talvez você ainda possa adiar: quantos tipos de embarcações ou localizações você adicionará, de forma realista? De qualquer forma, quando você precisa equilibrar sua desduplicação com clareza, vale a pena assistir a esta demonstração de Katrina Owen.
De volta ao meu próprio exemplo feio: Desnecessário dizer que os benefícios da desduplicação nem estão sendo percebidos aqui. Enquanto isso, quanto custou?
Além de levar mais tempo para escrever em primeiro lugar, agora é um pouco menos trivial para ler, depurar e manter. Imagine o nível de legibilidade com uma quantidade moderada de duplicação permitida. Por exemplo, ter cada uma das quatro variações de versos soletradas.
Mas ainda não otimizamos!
Agora que seu algoritmo foi implementado e você provou que sua saída está correta, parabéns! Você tem uma base!
Finalmente, é hora de... otimizar, certo? Não, ainda Não faça isso ainda . É hora de pegar sua linha de base e fazer um bom benchmark . Defina um limite para suas expectativas em relação a isso e coloque-o em seu conjunto de testes. Então, se algo de repente tornar esse código mais lento - mesmo que ainda funcione - você saberá antes que ele saia pela porta.
Ainda adie a otimização, até que você tenha uma parte inteira da experiência do usuário relevante implementada. Até esse ponto, você pode estar direcionando uma parte totalmente diferente do código do que você precisa.
Vá terminar seu aplicativo (ou componente), se ainda não o fez, definindo todas as suas linhas de base de benchmark algorítmicos à medida que avança.
Feito isso, este é um ótimo momento para criar e avaliar testes de ponta a ponta cobrindo os cenários de uso do mundo real mais comuns do seu sistema.
Talvez você descubra que está tudo bem.
Ou talvez você tenha determinado que, em seu contexto da vida real, algo é muito lento ou consome muita memória.
OK, agora você pode otimizar
Há apenas uma maneira de ser objetivo sobre isso. É hora de desenvolver gráficos de chama e outras ferramentas de criação de perfil. Engenheiros experientes podem ou não adivinhar melhor com mais frequência do que novatos, mas esse não é o ponto: a única maneira de saber com certeza é traçar o perfil. Esta é sempre a primeira coisa a fazer no processo de otimização do código para desempenho.
Você pode criar um perfil durante um determinado teste de ponta a ponta para obter o que realmente causará o maior impacto. (E mais tarde, após a implantação, monitorar os padrões de uso é uma ótima maneira de saber quais aspectos do seu sistema são os mais relevantes para medir no futuro.)
Observe que você não está tentando usar o criador de perfil em toda a sua profundidade - você está procurando mais por perfil de nível de função do que perfil de nível de instrução, geralmente, porque seu objetivo neste momento é apenas descobrir qual algoritmo é o gargalo .
Agora que você usou a criação de perfil para identificar o gargalo do seu sistema, agora você pode realmente tentar otimizar, confiante de que sua otimização vale a pena. Você também pode provar o quão eficaz (ou ineficaz) sua tentativa foi, graças aos benchmarks de linha de base que você fez ao longo do caminho.
Técnicas Gerais
Primeiro, lembre-se de permanecer em alto nível o maior tempo possível:
Você sabia? O truque de otimização universal final, se aplica em todos os casos:
— Lars Doucet (@larsiusprime) 30 de março de 2017
- Desenhe menos coisas
- Atualizar menos coisas
No nível do algoritmo total, uma técnica é a redução de força. No caso de reduzir loops a fórmulas, lembre-se de deixar comentários. Nem todo mundo conhece ou se lembra de todas as fórmulas combinatórias. Além disso, tenha cuidado com o uso da matemática: às vezes, o que você acha que pode ser uma redução de força não é, no final. Por exemplo, vamos supor que x * (y + z)
tenha algum significado algorítmico claro. Se seu cérebro foi treinado em algum momento, por qualquer motivo, para desagrupar automaticamente termos semelhantes, você pode ficar tentado a reescrever isso como x * y + x * z
. Por um lado, isso coloca uma barreira entre o leitor e o significado algorítmico claro que estava lá. (Pior ainda, agora é realmente menos eficiente por causa da operação de multiplicação extra necessária. É como se o desenrolar do loop tivesse acabado com as calças.) Em qualquer caso, uma nota rápida sobre suas intenções ajudaria muito e poderia até ajudá-lo a ver seu próprio erro antes de cometê-lo.
Esteja você usando uma fórmula ou apenas substituindo um algoritmo baseado em loop por outro algoritmo baseado em loop, você está pronto para medir a diferença.
Mas talvez você possa obter melhor desempenho simplesmente alterando sua estrutura de dados. Informe-se sobre a diferença de desempenho entre as várias operações que você precisa fazer na estrutura que está usando e sobre quaisquer alternativas. Talvez um hash pareça um pouco mais confuso para funcionar no seu contexto, mas o tempo de pesquisa superior vale a pena em relação a uma matriz? Estes são os tipos de trade-offs que cabe a você decidir.
Você pode perceber que isso se resume a saber quais algoritmos estão sendo executados em seu nome quando você chama uma função de conveniência. Então é realmente a mesma coisa que redução de força, no final. E saber o que as bibliotecas do seu fornecedor estão fazendo nos bastidores é crucial não apenas para o desempenho, mas também para evitar bugs não intencionais.
Micro-otimizações
OK, a funcionalidade do seu sistema está pronta, mas do ponto de vista do UX, o desempenho pode ser ajustado um pouco mais. Supondo que você tenha feito tudo o que pode mais alto, é hora de considerar as otimizações que evitamos o tempo todo até agora. Considere, porque esse nível de otimização ainda é uma compensação em relação à clareza e manutenção. Mas você decidiu que é hora, então vá em frente com a criação de perfil em nível de instrução, agora que você está dentro do contexto de todo o sistema, onde realmente importa.
Assim como com as bibliotecas que você usa, inúmeras horas de engenharia foram colocadas em seu benefício no nível de seu compilador ou interpretador. (Afinal, a otimização do compilador e a geração de código são grandes tópicos próprios). Isso é verdade mesmo no nível do processador. Tentar otimizar o código sem estar ciente do que está acontecendo nos níveis mais baixos é como pensar que ter tração nas quatro rodas implica que seu veículo também pode parar mais facilmente.
É difícil dar bons conselhos genéricos além disso, porque realmente depende da sua pilha de tecnologia e do que seu criador de perfil está apontando. Mas, porque você está medindo, você já está em uma excelente posição para pedir ajuda, se as soluções não se apresentarem orgânica e intuitivamente a você a partir do contexto do problema. (O sono e o tempo gasto pensando em outra coisa também podem ajudar.)
Neste ponto, dependendo do contexto e dos requisitos de escala, Jeff Atwood provavelmente sugeriria simplesmente adicionar hardware, o que pode ser mais barato que o tempo do desenvolvedor.
Talvez você não vá por esse caminho. Nesse caso, pode ser útil explorar várias categorias de técnicas de otimização de código:
- Cache
- Hacks de bits e aqueles específicos para ambientes de 64 bits
- Otimização de loop
- Otimização da hierarquia de memória
Mais especificamente:
- Dicas de otimização de código em C e C++
- Dicas de otimização de código em Java
- Otimizando o uso da CPU no .NET
- Cache de farm da web ASP.NET
- Ajuste do banco de dados SQL ou ajuste do Microsoft SQL Server em particular
- Escalando o jogo de Scala! estrutura
- Otimização avançada de desempenho do WordPress
- Otimização de código com protótipo JavaScript e cadeias de escopo
- Otimizando o desempenho do React
- Eficiência de animação do iOS
- Dicas de desempenho do Android
De qualquer forma, tenho mais alguns nãos para você:
Não reutilize uma variável para vários propósitos distintos. Em termos de manutenção, é como dirigir um carro sem óleo. Apenas nas situações mais extremas isso fazia sentido e, mesmo nesses casos, eu diria que não faz mais. Este é o trabalho do compilador para organizar. Faça você mesmo, mova uma linha de código e você introduziu um bug. A ilusão de economizar memória vale isso para você?
Não use macros e funções inline sem saber o porquê. Sim, a sobrecarga de chamada de função é um custo. Mas evitá-lo geralmente torna seu código mais difícil de depurar e, às vezes, o torna mais lento. Usar essa técnica em todos os lugares só porque é uma boa ideia de vez em quando é um exemplo de martelo de ouro.
Não desenrole os laços à mão. Novamente, essa forma de otimização de loop é quase sempre melhor otimizada por um processo automatizado como a compilação, não sacrificando a legibilidade do seu código.
A ironia nos dois últimos exemplos de otimização de código é que eles podem realmente ser anti-desempenho. Claro, já que você está fazendo benchmarks, você pode provar ou refutar isso para seu código específico. Mas mesmo que você veja uma melhoria de desempenho, volte para o lado da arte e veja se o ganho compensa a perda de legibilidade e manutenção.
É seu: otimização otimizada
A tentativa de otimização de desempenho pode ser benéfica. Na maioria das vezes, porém, é feito muito prematuramente, traz consigo uma série de efeitos colaterais ruins e, mais ironicamente, leva a um desempenho pior. Espero que você tenha uma apreciação ampliada pela arte e ciência da otimização e, mais importante, seu contexto adequado.
Fico feliz se isso nos ajudar a abandonar a noção de escrever código perfeito desde o início e escrever o código correto. Devemos nos lembrar de otimizar de cima para baixo, provar onde estão os gargalos e medir antes e depois de corrigi-los. Essa é a estratégia ideal, ideal para otimizar a otimização. Boa sorte.