Sou desenvolvedor júnior de software e queria saber quando seria o melhor momento para otimizar um software para obter melhor desempenho (velocidade).
Supondo que o software não seja extremamente grande e complexo de gerenciar, é melhor gastar mais tempo no início para otimizá-lo ou devo apenas desenvolver o software que executa todas as funcionalidades corretamente e depois prosseguir para otimizá-lo para obter melhor desempenho?
Respostas:
A coisa número um deve ser sempre e para sempre legibilidade. Se for lento, mas legível, eu posso consertar. Se estiver quebrado, mas legível, posso corrigi-lo. Se é ilegível, tenho que perguntar a outra pessoa o que isso deveria fazer.
É notável o desempenho do seu código quando você estava focado apenas em ser legível. Tanto é assim que geralmente ignoro o desempenho até que haja um motivo para me importar. Isso não deve significar que eu não me importo com velocidade. Eu faço. Acabei de descobrir que existem muito poucos problemas cujas soluções são realmente mais rápidas quando difíceis de ler.
Apenas duas coisas me tiram desse modo:
De qualquer forma, evite a paralisia da análise fazendo-se pensar que não deveria tentar uma solução, pois ela pode não ser a mais rápida. Seu código será realmente beneficiado se você tentar várias soluções, pois as alterações farão com que você use um design que facilite a alteração. Uma base de código flexível pode ser feita mais rapidamente mais tarde, onde realmente precisa. Escolha a velocidade flexível e você pode escolher a velocidade que precisa.
fonte
Se um determinado nível de desempenho for necessário (um requisito não-funcional), esse deverá ser um objetivo de design desde o início. Por exemplo, isso pode influenciar quais tecnologias podem ser apropriadas ou como você estrutura o fluxo de dados no programa.
Mas, em geral, não é possível otimizar antes que o código seja escrito: primeiro faça-o funcionar, depois faça-o correto e, finalmente, faça-o rápido .
Um grande problema com a otimização antes de implementar a maioria das funcionalidades é que você se prendeu a decisões de design abaixo do ideal nos lugares errados. Freqüentemente (mas não necessariamente) existe uma troca entre manutenção e desempenho. A maioria das partes do seu programa é totalmente irrelevante para o desempenho! Os programas típicos têm apenas alguns pontos de acesso que realmente valem a pena otimizar. Portanto, sacrificar a capacidade de manutenção pelo desempenho em todos os lugares que não precisam de desempenho é uma prática muito ruim.
Otimizar para manutenção é a melhor abordagem. Se você gastar sua inteligência em manutenções e desenhos claros, será mais fácil, a longo prazo, identificar seções críticas e otimizá-las com segurança sem comprometer o design geral.
fonte
Comece removendo da sua mente o conceito de que desempenho é a mesma coisa que velocidade. Desempenho é o que o usuário acredita que é desempenho .
Se você fizer um aplicativo responder duas vezes mais rápido a um clique do mouse e passar de dez microssegundos a cinco microssegundos, o usuário não se importará. Se você faz um aplicativo responder duas vezes mais rápido a um clique do mouse e passa de quatro mil anos a dois mil anos, novamente, o usuário não se importa.
Se você tornar seu aplicativo duas vezes mais rápido e consumir toda a memória da máquina e travar, o usuário não se importará que agora seja duas vezes mais rápido.
O desempenho é a ciência de fazer trocas efetivas sobre o consumo de recursos para obter uma experiência específica do usuário. O tempo do usuário é um recurso importante , mas nunca é apenas "mais rápido". Atingir as metas de desempenho quase sempre exige trocas, e elas geralmente trocam tempo por espaço ou vice-versa.
Essa é uma suposição terrível.
Se o software não é grande e complexo de gerenciar, provavelmente não resolve um problema interessante com o qual o usuário se importa, e provavelmente é super fácil de otimizar.
Você está sentado em uma página em branco e escreve
void main() {}
Você começa a otimizar? Não há nada para otimizar! A ordem correta é:Se você tentar fazer isso em qualquer outra ordem, você acaba com o código errado, que é uma bagunça, e agora você tem um programa que produz respostas erradas muito rapidamente e resiste às mudanças.
Mas há um passo faltando lá. A verdadeira ordem correta é:
fonte
Como regra geral, é melhor otimizar o desempenho posteriormente, mas já vi muitos projetos falharem quando os desenvolvedores percebem que acabaram com um software que fica lento quando qualquer carga ou dados significativos são adicionados a ele.
Portanto, uma abordagem de meio termo seria melhor na minha opinião; não coloque muita ênfase nisso, mas não desconsidere o desempenho por completo.
Vou dar um exemplo que já vi muitas vezes; dada uma biblioteca ORM, temos uma entidade Usuário que pode ter um ou mais pedidos. Vamos fazer um loop em todos os pedidos de um usuário e descobrir quanto ele gastou em nossa loja - uma abordagem ingênua:
Vi desenvolvedores escreverem coisas semelhantes, sem pensar nas implicações; primeiro obtemos o usuário, que, esperançosamente, será apenas uma consulta SQL na tabela Usuário (mas pode envolver muito, muito mais), depois percorremos os pedidos, o que pode incluir a obtenção de todos os dados relevantes para todas as linhas de pedido do pedido , informações do produto etc. - tudo isso apenas para obter um número inteiro único para cada pedido!
A quantidade de consultas SQL aqui pode surpreendê-lo. Obviamente, depende de como suas entidades estão estruturadas.
Aqui, a abordagem correta provavelmente incluiria uma função separada para obter a soma do banco de dados por meio de uma consulta separada, escrita na linguagem de consulta fornecida pelo ORM, e eu recomendaria isso da primeira vez e não adiar isso para mais tarde; porque se você fizer isso, provavelmente terá muito mais problemas para resolver e não saberá por onde começar.
fonte
O desempenho total do sistema é um produto das interações complexas da totalidade dos componentes do sistema. É um sistema não linear. Portanto, o desempenho será determinado não apenas pelo desempenho individual dos componentes, mas também pelo gargalos entre eles.
Obviamente, você não pode testar gargalos se todos os componentes do seu sistema ainda não foram construídos, portanto, não é possível testar muito bem desde o início. Por outro lado, após a construção do sistema, talvez você não ache tão fácil fazer as alterações necessárias para obter o desempenho desejado. Portanto, este é um Catch-22 completo .
Para tornar as coisas mais difíceis, seu perfil de desempenho pode mudar drasticamente quando você muda para um ambiente de produção, que geralmente não está disponível desde o início.
Então, o que você faz? Bem, algumas coisas.
Seja pragmático. Desde o início, você pode optar por usar os recursos da plataforma que são "práticas recomendadas" para desempenho; por exemplo, utilize o pool de conexões, transações assíncronas e evitando o estado, que pode ser a morte de um aplicativo multithread em que diferentes trabalhadores estão disputando o acesso a dados compartilhados. Normalmente você não testaria esses padrões quanto ao desempenho, apenas saberia por experiência própria o que funciona bem.
Seja iterativo. Tome medidas de desempenho de linha de base quando o sistema for relativamente novo e teste novamente ocasionalmente para garantir que o código recém-introduzido não prejudique muito o desempenho.
Não otimize demais cedo. Você nunca sabe o que vai ser importante e o que não vai importar; um algoritmo de análise de cadeia super rápida pode não ajudar se o seu programa estiver constantemente aguardando E / S, por exemplo.
Especialmente em aplicativos da Web, você pode se concentrar não tanto no desempenho, mas na escalabilidade. Se o aplicativo pode ser expandido, o desempenho quase não importa, pois você pode continuar adicionando nós ao seu farm até que seja rápido o suficiente.
Atenção especial vai para o banco de dados. Devido a restrições de integridade transacional, o banco de dados tende a ser um gargalo que domina todas as partes do sistema. Se você precisar de um sistema de alto desempenho, verifique se possui pessoas talentosas trabalhando no banco de dados, revisando os planos de consulta e desenvolvendo estruturas de tabela e índice que tornarão as operações comuns o mais eficientes possível.
A maioria dessas atividades não é para o início ou o fim do projeto, mas deve ser acompanhada continuamente .
fonte
Entenda que existem 2 extremos muito diferentes.
O primeiro extremo são coisas que afetam grande parte do design, como dividir o trabalho em quantos processos e / ou threads e como as peças se comunicam (soquetes TCP / IP? Chamadas diretas de função?), Seja para implementar um JIT avançado ou um intérprete "um opcode de cada vez", ou se planejar estruturas de dados para serem compatíveis com o SIMD, ou ... Essas coisas tendem a ter uma forte influência na implementação e tornam-se excessivamente difíceis / caras para adaptação posterior.
O outro extremo são as micro-otimizações - pequenos ajustes em todos os lugares. Essas coisas tendem a quase não ter influência na implementação (e geralmente são melhor executadas por um compilador de qualquer maneira), e é trivial fazer essas otimizações sempre que lhe apetecer.
Entre esses extremos, há uma enorme área cinzenta.
O que realmente se resume é a experiência / suposições educadas sendo usadas para responder a uma pergunta "os benefícios justificam os custos". Para otimizações no / quase um extremo, se você adivinhar errado com frequência, isso significa desperdiçar todo o seu trabalho e reiniciar do zero ou falha no projeto (muito tempo gasto em um design desnecessariamente complicado demais). No / próximo ao outro extremo, é muito mais sensato deixá-lo até que você possa provar que é importante usando a medição (por exemplo, criação de perfil).
Infelizmente, vivemos em um mundo onde muitas pessoas pensam que a otimização inclui apenas as coisas (principalmente irrelevantes) no extremo "trivial".
fonte
É mais fácil escrever código que não seja porformante nem sustentável. É mais difícil escrever código de executor. Ainda é mais difícil escrever código sustentável. E é o mais difícil escrever código que possa ser mantido e com desempenho.
Porém, é mais fácil tornar o código de desempenho com manutenção, do que tornar o código de desempenho com manutenção.
Agora, obviamente, depende do tipo de sistema que você está criando, alguns sistemas serão altamente críticos em termos de desempenho e precisam ser planejados desde o início. Para pessoas extremamente talentosas como Eric Lippert, que responderam acima, esses sistemas podem ser comuns; mas para a maioria de nós, eles são a minoria dos sistemas que construímos.
No entanto, dado o estado do hardware moderno, na maioria dos sistemas, não é necessário prestar atenção especial à otimização desde o início, mas evitar a destruição do desempenho geralmente é suficiente. O que quero dizer com isso é: evite fazer coisas completamente estúpidas, como trazer de volta todos os registros de uma tabela para obter uma contagem em vez de apenas consultar
select count(*) from table
. Evite cometer erros e faça um esforço para entender as ferramentas que você está usando.Em seguida, primeiro foque em tornar seu código sustentável. Com isso, quero dizer:
O código de manutenção é muito mais fácil de otimizar quando as estatísticas mostram que é necessário.
Em seguida, verifique se o seu código possui MUITOS testes automatizados, isso tem vários benefícios. Menos bugs significa mais tempo para otimizar, quando necessário . Além disso, ao otimizar, é possível iterar e encontrar a melhor solução muito mais rapidamente, pois você encontra erros em suas implementações muito mais rapidamente.
Os scripts de implantação automatizada e a infraestrutura com script também são muito úteis para o ajuste do desempenho, pois, novamente, eles permitem a iteração mais rápida; para não mencionar seus outros benefícios.
Portanto, como sempre, há exceções (que você precisará de experiência para identificar melhor), mas, em geral, meu conselho é: primeiro, aprenda suas ferramentas e evite codificar gargalos de desempenho. Segundo, verifique se o código é sustentável. Terceiro, testes automatizados. Quarto, implantações totalmente automatizadas. Somente depois que essas coisas forem feitas, você deverá se preocupar com a otimização.
fonte
Eu posso ser tendencioso trabalhando em áreas muito críticas de desempenho, como processamento de imagem e rastreamento de raios, mas ainda assim diria para otimizar "o mais tarde possível" . Não importa quão críticos sejam o desempenho de seus requisitos, sempre há muito mais informações e clareza em retrospectiva, depois da avaliação, do que antes, o que significa que mesmo as otimizações mais eficazes são normalmente aplicadas mais tarde após o ganho de tal conhecimento.
Casos peculiares
Mas, às vezes, "o mais tarde possível" ainda é bem cedo em alguns casos peculiares. Se estivermos falando de renderizadores offline, por exemplo, as estruturas e técnicas de dados que você usa para obter desempenho realmente se infiltram no design final do usuário. Isso pode parecer nojento, mas o campo é tão avançado e tão crítico quanto ao desempenho que os usuários aceitam controles finais do usuário específicos para as técnicas de otimização aplicáveis a um determinado traçador de raios (ex: cache de irradiância ou mapeamento de fótons), já que alguns deles são usados até horas de espera para que uma imagem seja renderizada, e outros são usados para gastar enormes somas de dinheiro para alugar ou possuir uma fazenda de renderização com máquinas dedicadas à renderização. Há uma enorme redução de tempo e dinheiro para esses usuários, se um renderizador offline competitivo puder oferecer uma redução não trivial no tempo gasto na renderização. Esse é um tipo de área em que uma redução de 5% no tempo realmente excita os usuários.
Em casos tão peculiares, você não pode escolher apenas uma técnica de renderização, querendo ou não, e esperá-la otimizá-la mais tarde, pois todo o design, incluindo o design do usuário, gira em torno das estruturas de dados e algoritmos que você usa. Você não pode necessariamente ir apenas com o que funcionou bem para outras pessoas, pois aqui, você, como indivíduo, e seus pontos fortes e fracos, são muito importantes para fornecer uma solução competitiva. A mentalidade e a sensibilidade do desenvolvedor principal por trás de Arnold são diferentes daqueles que trabalham no VRay que usaram uma abordagem muito diferente; eles não podem necessariamente trocar de lugar / técnica e fazer o melhor trabalho (mesmo sendo os dois líderes industriais). Você precisa experimentar e fazer protótipos e referências e encontrar o que deseja. você é particularmente bom em fazer, dada a infinita variedade de técnicas de ponta disponíveis, se você espera lançar algo competitivo que realmente venderá. Portanto, nesse caso peculiar, as preocupações com o desempenho avançam para a frente como talvez a preocupação mais importante antes mesmo de iniciar o desenvolvimento.
Ainda assim, isso não é necessariamente uma violação da otimização "o mais tarde possível" , é apenas "o mais tarde possível" é bastante cedo nesses casos extremos e peculiares. Descobrir quando e também o que não precisa de preocupações tão precoces de desempenho, se é que alguma vez é, provavelmente é o principal desafio para o desenvolvedor. O que não otimizar pode ser uma das coisas mais valiosas para aprender e continuar aprendendo na carreira de um desenvolvedor, já que não há falta de desenvolvedores ingênuos que desejam otimizar tudo (e, infelizmente, até alguns veteranos que conseguiram, de alguma forma, manter seu emprego em apesar de sua contra-produtividade).
O mais tarde possível
Talvez a parte mais difícil seja tentar entender o que isso significa. Ainda estou aprendendo e programando há quase três décadas. Mas especialmente agora na minha terceira década, estou começando a perceber que não é tão difícil. Não é ciência do foguete, se você se concentrar mais no design do que na implementação. Quanto mais seus projetos deixarem espaço para otimizações apropriadas posteriormente, sem alterações no projeto, mais tarde você poderá otimizar. E quanto mais produtividade ganhei buscando projetos que me proporcionassem esse espaço para respirar.
Design que oferece espaço para otimizar mais tarde
Na verdade, esses tipos de projetos não são tão difíceis de conseguir na maioria dos casos, se podemos aplicar algum "bom senso". Como uma história pessoal, gosto de artes visuais como hobby (acho que ajuda um pouco a programar software para artistas sendo um pouco eu para entender suas necessidades e falar sua língua), e passei algum tempo no início dos anos 2000 usando applets Oekaki on-line como uma maneira rápida de rabiscar, compartilhar meu trabalho e me conectar com outros artistas.
Em particular, meu site e applet favorito estavam repletos de falhas de desempenho (qualquer tamanho de pincel não trivial seria lento para rastrear), mas tinha uma comunidade muito boa. Para solucionar os problemas de desempenho, usei pequenos pincéis de 1 ou 2 pixels e escrevi meu trabalho da seguinte maneira:
Enquanto isso, continuava dando sugestões ao autor do software para melhorar o desempenho, e ele percebeu que minhas sugestões eram de natureza particularmente técnica, falando sobre otimizações de memória e algoritmos e assim por diante. Então ele realmente perguntou se eu era um programador e eu disse que sim e ele me convidou para trabalhar no código fonte.
Então, olhei o código-fonte, executei-o, criei um perfil e, para meu horror, ele projetou o software em torno do conceito de uma "interface abstrata de pixel", como
IPixel
, que acabou sendo a causa raiz dos principais hotspots para tudo com dinâmica alocações e envio para cada pixel de cada imagem. No entanto, não havia uma maneira prática de otimizar isso sem reconsiderar todo o design do software, porque o design o aprisionara em um canto em que não há muito além das micro-otimizações mais triviais quando nossas abstrações estão trabalhando no nível granular de um único pixel abstrato e tudo depende desse pixel abstrato.Eu acho que isso é uma violação do "senso comum", mas obviamente não era um senso comum para o desenvolvedor. Mas é como não abstrair as coisas em um nível tão granular, onde até os casos de uso mais básicos serão instanciados aos milhões, como pixels, partículas ou pequenas unidades em uma simulação de exército gigantesca. Favorecer
IImage
(você pode lidar com todos os formatos de imagem / pixel necessários nesse nível agregado mais volumoso) ouIParticleSystem
paraIPixel
orIParticle
e, em seguida, você pode colocar as implementações mais básicas, de gravação rápida e de fácil compreensão por trás dessas interfaces e tenha todo o espaço para respirar que você precisará otimizar mais tarde, sem reconsiderar todo o design do software.E esse é o objetivo que vejo hoje em dia. Excluindo os casos peculiares, como os renderizadores off-line acima, projete com espaço suficiente para otimizar o mais tarde possível, com o máximo de informações retrospectivas possível (incluindo medições) e aplique as otimizações necessárias o mais tarde possível.
É claro que não estou necessariamente sugerindo começar a usar algoritmos de complexidade quadrática em entradas que facilmente atingem um tamanho não trivial em casos comuns de usuários finais. Quem faz isso afinal? Mas nem acho que isso seja tão importante se a implementação for fácil de trocar mais tarde. Isso ainda não é um erro grave se você não precisar reconsiderar nenhum design.
fonte
Depende do que esse desempenho significa para o seu aplicativo. E se é possível otimizar o desempenho antes que o aplicativo esteja funcionalmente completo.
Na maioria das vezes, você não deve se preocupar com isso até não ter nada melhor para fazer, mas pode ser que um certo nível de desempenho seja crítico para o sucesso do seu aplicativo. Se for esse o caso e você suspeitar que pode não ser fácil, comece a analisar o desempenho com o objetivo de "falhar rápido".
Um princípio importante para qualquer projeto é focar primeiro nas partes mais difíceis. Dessa forma, se acontecer que você não pode fazê-lo, você saberá cedo e haverá tempo para tentar algo totalmente diferente ou o projeto poderá ser cancelado antes de ter sido gasto muito com isso.
fonte
Vou sugerir desempenho é mais do que velocidade. Inclui escala (centenas a milhares de usuários simultâneos). Certamente você não deseja que o aplicativo seja armazenado quando receber uma carga de produção. O desempenho inclui a quantidade de recursos (por exemplo, memória) que o aplicativo consome.
O desempenho também é fácil de usar. Alguns usuários preferem que 1 pressionamento de tecla execute uma tarefa em 10 segundos, e 2 pressionamentos de tecla executem a tarefa em 1 segundo. Para coisas assim, pergunte ao seu líder de design. Não gosto de levar coisas assim aos usuários cedo. No vácuo, eles podem dizer X, mas uma vez que estão trabalhando com um pré-lançamento funcional, podem dizer Y.
A melhor velocidade individual é manter um recurso, como uma conexão com o banco de dados. Mas, para a escala, você deve adquirir a conexão o mais tarde possível e liberá-la o mais rápido possível. Uma viagem ao banco de dados para obter três coisas é mais rápida que três viagens separadas ao banco de dados.
Você está procurando informações que não mudam durante a sessão. Se assim for, obtenha-o na sessão inicie e segure é memória.
Ao escolher o tipo de coleção, considere o funcional, a velocidade e o tamanho.
Tem certeza de que precisa reter os itens em uma coleção? Um problema comum é ler todas as linhas de um arquivo em uma lista e depois processar a lista uma linha por vez. É muito mais eficiente ler o arquivo uma linha de cada vez e pular a lista.
Você está repetindo três vezes quando pode repetir uma vez e fazer três coisas.
Existe um local em que você pode precisar processar em outro segmento com uma chamada de retorno. Nesse caso, empacote o código com essa possível necessidade em mente, se não interferir nas necessidades imediatas de design.
Muito desempenho também é um código limpo.
Existe uma otimização prematura e há apenas um senso de senso comum que realmente não leva mais tempo.
No banco de dados é onde vejo otimização prematura. Des normalizará a velocidade antes que haja um problema de velocidade. O argumento que recebo é que, se mudarmos a tabela posteriormente, teremos que mudar tudo. Freqüentemente, você pode criar uma exibição que apresenta os dados dessa maneira e talvez seja necessário trocar por uma tabela desnormalizada posteriormente.
fonte