Por que é tão ruim otimizar muito cedo?

168

Depois de analisar um pouco a otimização, descobri (literalmente em todos os lugares) que parece um pecado universalmente reconhecido otimizar um jogo muito cedo.

Eu realmente não entendo isso, não seria incrivelmente difícil mudar algumas das estruturas principais do jogo no final, ao invés de desenvolvê-las pela primeira vez com o desempenho em mente?

Eu espero que até o final do jogo lhe diga se você precisa de otimizações, mas de qualquer maneira, afinal, isso poderia aumentar a variedade de dispositivos nos quais o jogo poderia rodar, o que aumentaria o número de possíveis jogadoras.

Alguém poderia me explicar por que é tão ruim otimizar muito cedo?

Sr. Matt
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Josh
3
Não poderia ser mais fácil responder a isso: "desperdiça tempo". Você também pode perguntar "por que tentar economizar dinheiro ao fazer compras?"
Fattie
27
Por outro lado, observe que muitos engenheiros simplesmente dizem que a frase "não otimize muito cedo" está simplesmente errada . A frase deve ser simplesmente "não otimizar até que você saiba o que otimizar". Então, simplesmente, se existem três sistemas A, B e C, é totalmente inútil otimizar A se, ao que parece, A não é de forma alguma um gargalo, e C é de fato o gargalo. Nesse exemplo, obviamente, otimizar A teria sido uma perda total de tempo. Portanto - simplesmente - não otimize até saber qual de A, BC otimizar: é tudo o que isso significa, é simples assim.
Fattie
2
Otimizar a classe de tempo do seu aplicativo durante o desenvolvimento é muito diferente de tentar raspar algumas instruções de um loop, que geralmente é tempo constante. Concentre-se em O (n) vs O (n log n) em vez de "escrever um loop for dessa maneira salva uma instrução da CPU".
1
@phyrfox Na verdade, reduzir o tempo de carregamento parece mais impressionante para mim. O tempo de carregamento de três segundos é perceptível. Não tenho certeza se 55fps a 60fps é perceptível.
immibis 28/05

Respostas:

237

Preâmbulo:

Algumas objeções foram levantadas nos comentários, e acho que elas derivam em grande parte de um mal-entendido do que queremos dizer quando dizemos "otimização prematura" - então eu queria acrescentar um pouco de esclarecimento sobre isso.

"Não otimizar prematuramente" não significa "escrever código que você sabe que é ruim, porque Knuth diz que você não pode limpá-lo até o final"

Significa "não sacrifique tempo e legibilidade para otimização até que você saiba quais partes do seu programa realmente precisam de ajuda para serem mais rápidas". Como um programa típico passa a maior parte do tempo em alguns gargalos, investir na otimização de "tudo" pode não lhe proporcionar o mesmo aumento de velocidade, pois concentra esse mesmo investimento apenas no código de gargalo.

Isso significa que, em caso de dúvida , devemos:

  • Prefira código simples de escrever, claro de entender e fácil de modificar para iniciantes

  • Verifique se é necessária uma otimização adicional (geralmente criando um perfil do programa em execução, embora um comentário abaixo faça anotações em análise matemática - o único risco que existe, você também precisa verificar se sua matemática está correta)

Uma otimização prematura não é :

  • Decisões arquitetônicas para estruturar seu código de forma que atenda às suas necessidades - escolhendo módulos / responsabilidades / interfaces / sistemas de comunicação apropriados de uma maneira considerada.

  • Eficiências simples que não levam tempo extra ou dificultam a leitura do código. Coisas como usar digitação forte podem ser eficientes e tornar sua intenção mais clara. O armazenamento em cache de uma referência em vez de procurá-la repetidamente é outro exemplo (desde que o seu caso não exija uma lógica complexa de invalidação de cache - talvez você não precise escrever isso até que você crie o perfil da maneira mais simples primeiro).

  • Usando o algoritmo certo para o trabalho. A * é mais ideal e mais complexo do que pesquisar exaustivamente um gráfico de busca de caminhos. É também um padrão da indústria. Repetindo o tema, a adesão a métodos testados e comprovados como esse pode facilitar a compreensão do código do que se você fizer algo simples, mas contrário às práticas recomendadas conhecidas. Se você tiver experiência em encontrar gargalos na implementação do recurso X do jogo de uma maneira em um projeto anterior, não precisará acertar o mesmo gargalo novamente neste projeto para saber que é real - você pode e deve reutilizar soluções que funcionaram no passado jogos

Todos esses tipos de otimizações são bem justificáveis ​​e geralmente não seriam rotulados de "prematuros" (a menos que você esteja descendo uma toca de coelho implementando a busca de caminhos de ponta no seu mapa de tabuleiro de xadrez 8x8 ...)

Agora, com isso esclarecido, sobre por que podemos achar essa política útil em jogos especificamente:


Em gamedev, especialmente, a velocidade da iteração é a coisa mais preciosa. Muitas vezes, implementamos e reimplementamos muito mais idéias do que as que acabam sendo fornecidas com o jogo finalizado, tentando "encontrar a diversão".

Se você pode criar um protótipo de um mecânico de maneira direta e talvez um pouco ingênua e testá-lo no dia seguinte, você está em uma posição muito melhor do que se passasse uma semana criando a versão mais ideal primeiro. Especialmente se isso for péssimo e você acabar descartando esse recurso. Fazer isso da maneira mais simples para que você possa testar cedo pode economizar uma tonelada de trabalho desperdiçado, otimizando o código que você não mantém.

O código não otimizado também é geralmente mais fácil de modificar e experimentar diferentes variantes do que o código ajustado para fazer uma coisa precisa de maneira ideal, que tende a ser quebradiça e mais difícil de modificar sem quebrá-lo, introduzir bugs ou diminuir a velocidade. Portanto, manter o código simples e fácil de mudar geralmente vale um pouco da ineficiência do tempo de execução durante a maior parte do desenvolvimento (normalmente desenvolvemos máquinas acima da especificação de destino, para que possamos absorver a sobrecarga e nos concentrar em obter a experiência de destino primeiro) até que bloqueou o que precisamos do recurso e pode otimizar as peças que sabemos agora serem lentas.

Sim, refatorar partes do projeto com atraso no desenvolvimento para otimizar os pontos lentos pode ser difícil. Mas o mesmo acontece com a refatoração repetida ao longo do desenvolvimento, porque as otimizações que você fez no mês passado não são compatíveis com a direção em que o jogo evoluiu desde então, ou estavam consertando algo que acabou não sendo o gargalo real, uma vez que você obtém mais recursos e conteúdo no.

Os jogos são estranhos e experimentais - é difícil prever como um projeto de jogo e suas necessidades tecnológicas evoluirão e onde o desempenho será mais apertado. Na prática, muitas vezes acabamos nos preocupando com as coisas erradas - pesquise nas perguntas de desempenho aqui e você verá um tema comum emergindo dos desenvolvedores sendo distraídos por coisas no papel que provavelmente não são um problema.

Para dar um exemplo dramático: se o seu jogo for vinculado à GPU (não incomum), todo esse tempo gasto com a otimização e o enfoque no trabalho da CPU poderá não trazer benefícios tangíveis. Todas essas horas de desenvolvimento poderiam ter sido gastas implementando e aprimorando os recursos de jogabilidade, para uma melhor experiência do jogador.

No geral, a maior parte do tempo que você gasta trabalhando em um jogo não será gasta no código que acaba sendo o gargalo. Especialmente quando você está trabalhando em um mecanismo existente, o material de loop interno super caro nos sistemas de renderização e física está em grande parte fora de suas mãos. Nesse ponto, seu trabalho nos scripts de jogabilidade é basicamente ficar fora do caminho do mecanismo - desde que você não jogue uma chave lá, então provavelmente sairá bem para a primeira compilação.

Portanto, além de um pouco de higiene e orçamento do código (por exemplo, não procure / construa repetidamente coisas, se você puder reutilizá-las facilmente, mantenha modestas as consultas de localização / física ou as leituras de GPU, etc.), criando o hábito de não exagerar. - otimizar antes que saibamos onde estão os problemas reais para melhorar a produtividade - poupando-nos de perder tempo otimizando as coisas erradas e mantendo nosso código mais simples e fácil de ajustar em geral.

DMGregory
fonte
38
Há algumas discussões adicionais sobre o StackOverflow aqui , enfatizando a importância da legibilidade e clareza no código e o perigo de tornar o código mais difícil de entender (e mais fácil de introduzir erros), otimizando-o prematuramente.
DMGregory
8
Ótima resposta, gostaria de acrescentar, em resposta a " não seria incrivelmente difícil mudar algumas das estruturas principais do jogo no final, ao invés de desenvolvê-las pela primeira vez com o desempenho em mente? " é claro que você tenta projetar para obter eficiência em primeiro lugar. Quando lhe são apresentadas duas opções, você escolhe a mais eficiente. Na sua falta, você escreve o jogo da forma mais modular possível, com interfaces bem definidas. Você pode alterar os mecanismos em cada módulo sem reestruturar toda a base de código.
Baldrickk
1
@Baldrickk, eu diria que vale a pena compartilhar como resposta adicional. (Eu recomendo!) Minha resposta ignora a importância da arquitetura e do planejamento para evitar a criação de grandes problemas em primeiro lugar. Quanto à referência do Gizmo ao "desenvolvimento de programas em geral", é daí que vem esse conceito de otimização prematura. Conforme descrito acima, a otimização errada pode se tornar uma dívida técnica tão facilmente quanto uma otimização ausente. Mas devemos enfatizar que nós nunca estamos fazendo as coisas de uma forma deliberadamente lenta, apenas preferindo simplicidade e legibilidade até que possamos Perfil e encontrar os verdadeiros problemas
DMGregory
1
Embora eu seja provavelmente o mais inexperiente aqui dentre vocês, devo dizer que acho essa "raiz prematura de otimização de todo mal" um pouco estranha. Dois anos atrás, eu estava tentando fazer algo no Unity3D. Eu escrevi algumas consultas LINQ, cada uma usando a anterior. Eu olhei para o meu código e comecei a ter fortes dúvidas. Eu pensei que a complexidade da computação era horrível. Peguei uma folha de papel e calculei que o processamento dessas consultas em um tamanho esperado de dados levaria horas. Então eu adicionei um pouco de cache aqui e ali. E as consultas estavam indo bem.
gaazkam
3
Portanto, você fez a devida diligência e verificou que o problema era real antes de alterar seu código do que era inicialmente mais claro e intuitivo para você. Isso significa que sua otimização não foi prematura. ;) "Evitar a otimização excessiva prematuramente" não significa "escrever código desnecessariamente caro" - apenas para favorecer a simplicidade, legibilidade e facilidade de modificação até que um problema definido de desempenho seja identificado. Os comentários não se destinam à discussão, portanto, podemos passar para o bate-papo, se você quiser falar mais sobre isso.
DMGregory
42

nota: esta resposta começou como um comentário na resposta do DMGregory e, portanto, não duplica os pontos muito bons que ele faz.

"Não seria incrivelmente difícil mudar algumas das estruturas principais do jogo no final, em vez de desenvolvê-las pela primeira vez com o desempenho em mente?"

Este, para mim, é o cerne da questão.

Ao criar seu design original, você deve tentar projetar com eficiência - no nível superior. Isso é menos otimização e tem mais a ver com estrutura.

Exemplo:
você precisa criar um sistema para atravessar um rio. Os desenhos óbvios são uma ponte ou uma balsa, então qual você escolhe?
A resposta, é claro, depende do tamanho da travessia e do volume de tráfego. Isso não é uma otimização, é começar com um design adequado ao seu problema.

Quando lhe são apresentadas as opções de design, você escolhe o que melhor se adequa ao que deseja fazer.

Então, digamos que nosso volume de tráfego seja bastante baixo, decidimos construir dois terminais e comprar uma balsa para lidar com o tráfego. Uma implementação simples e agradável
Infelizmente, uma vez instalada e funcionando, descobrimos que ele está vendo mais tráfego do que o esperado. Precisamos otimizar a balsa! (porque funciona, e construir uma ponte agora não é um bom plano)

Opções:

  • Compre uma segunda balsa (processamento paralelo)
  • Adicionar outro deck de carro para transportar (compressão de tráfego)
  • Atualize o mecanismo da balsa para torná-lo mais rápido (algoritmos de processamento reescritos)

É aqui que você deve tentar tornar seu design original o mais modular possível.
Todas as alternativas acima são possíveis otimizações e você pode até fazer as três.
Mas como você faz essas mudanças sem grandes mudanças estruturais?

Se você possui um design modular com interfaces claramente definidas, deve ser simples implementar essas alterações.
Se o seu código não estiver totalmente acoplado, as alterações nos módulos não afetarão a estrutura circundante.

Vamos dar uma olhada em adicionar uma balsa extra.
Um programa "ruim" pode ser construído em torno da idéia de uma única balsa e ter os estados das docas e o estado da balsa e posicionar todos agrupados e compartilhar o estado. Isso será difícil de modificar para permitir que uma balsa extra seja adicionada ao sistema.
Um projeto melhor seria ter as docas e a balsa como entidades separadas. Não há um acoplamento estreito entre eles, mas eles têm uma interface, onde uma balsa pode chegar, descarregar passageiros, pegar novos e sair. A doca e a balsa compartilham apenas essa interface, e isso facilita a alteração do sistema, neste caso, adicionando uma segunda balsa. O banco dos réus não se importa com o que há de fato nas balsas, tudo o que se preocupa é que algo (qualquer coisa) esteja usando sua interface.

tl; dr:

  • Tente projetar para obter eficiência em primeiro lugar.
  • Quando lhe são apresentadas duas opções, você escolhe a mais eficiente.
  • Escreva seu código da maneira mais modular possível, com interfaces bem definidas.

Você pode alterar os mecanismos em cada módulo sem reestruturar toda a base de código quando precisar otimizar.

Baldrickk
fonte
16
No início, você também pode implementar sua balsa como IRiverCrossinge, quando ficar claro que o volume de tráfego é muito grande para a balsa configurada, implemente a ponte IRiverCrossinge solte-a. O truque é reduzir a API externa da balsa e a ponte para a funcionalidade comum, para que eles possam ser representados pela mesma interface.
Mr.Mindor
2
Você pode até ter testes automatizados escritos para essas interfaces modulares, para refatorar rapidamente, sem muita preocupação em quebrar qualquer coisa fora do módulo que estiver tocando.
User3067860
2
Muito bom exemplo de por que a OOP é tão importante nos dias de hoje.
Jaime Gallego
1
Você acertou no motivo certo. O mantra de Donald Knuth só é verdadeiro quando a velocidade não é um dos requisitos principais e o foco na otimização comprometeria o design principal. Infelizmente, nos jogos a velocidade geralmente está no centro e, portanto, deve ser levada em consideração no design desde o início. A preocupação do OP (e sua resposta) é inteiramente justificada.
Peter A. Schneider
3
@AlexandreVaillancourt, em vez de responder ao título da pergunta, tentei responder o que acho que é a parte principal da pergunta que está sendo respondida; isto é, "por que não otimizar cedo se otimizar será muito mais trabalhos depois porque meu design é fixo" (parafraseado) . Essa resposta também começou como um comentário na resposta do DMGregory e é uma adição a ela, e não há muito sentido em duplicar sua resposta.
precisa
24

"Não otimizar cedo" não significa "escolher a pior maneira possível de fazer as coisas". Você ainda precisa considerar as implicações de desempenho (a menos que esteja apenas fazendo prototipagem). A questão não é prejudicar outras coisas mais importantes naquele momento do desenvolvimento - como flexibilidade, confiabilidade etc. Escolha otimizações simples e seguras - escolha as coisas que você limita e as que mantém livres; acompanhar os custos. Você deve usar digitação forte? A maioria dos jogos funcionou bem; quanto custaria para remover isso se você encontrasse usos interessantes da flexibilidade do jogo?

É muito mais difícil modificar o código otimizado, especialmente o código "inteligente". É sempre uma escolha que melhora algumas coisas e outras piores (por exemplo, você pode estar trocando o tempo da CPU pelo uso de memória). Ao fazer essa escolha, você precisa estar ciente de todas as implicações - elas podem ser desastrosas, mas também podem ser úteis.

Por exemplo, o comandante Keen, Wolfenstein e Doom foram construídos sobre um mecanismo de renderização otimizado. Cada um deles teve seu "truque" que permitiu que o jogo existisse em primeiro lugar (cada um deles também teve outras otimizações desenvolvidas ao longo do tempo, mas isso não é importante aqui). Tudo bem . Não há problema em otimizar fortemente o núcleo do jogo, o pensamento que torna o jogo possível; especialmente se você estiver explorando um novo território, onde esse recurso otimizado específico permite considerar designs de jogos que não foram muito explorados. As limitações introduzidas pela otimização também podem oferecer uma jogabilidade interessante (por exemplo, os limites de contagem de unidades nos jogos RTS podem ter começado como uma maneira de melhorar o desempenho, mas também têm um efeito de jogabilidade).

Mas observe que em cada um desses exemplos, o jogo não poderia existir sem a otimização. Eles não começaram com um mecanismo "totalmente otimizado" - começaram com a simples necessidade e avançaram. Eles estavam desenvolvendo novas tecnologias e as usando para criar jogos divertidos. E os truques do mecanismo foram limitados à menor parte possível da base de código - as otimizações mais pesadas só foram introduzidas quando a jogabilidade estava concluída ou quando permitia o surgimento de um novo recurso interessante.

Agora considere um jogo que você pode querer fazer. Existe realmente algum milagre tecnológico que faz ou quebra esse jogo? Talvez você esteja imaginando um jogo de mundo aberto em um mundo infinito. Essa é realmente a peça central do jogo? O jogo simplesmente não funcionaria sem ele? Talvez você esteja pensando em um jogo em que o terreno é deformável sem limites, com geologia realista e outras coisas; você pode fazê-lo funcionar com um escopo menor? Funcionaria em 2D em vez de 3D? Obtenha algo divertido o mais rápido possível - mesmo que as otimizações exijam que você refaça uma grande parte do código existente, pode valer a pena; e você pode até perceber que aumentar as coisas realmente não melhora o jogo.

Como exemplo de um jogo recente com muitas otimizações, aponto para o Factorio. Uma parte crítica do jogo são os cintos - existem muitos milhares deles, e eles carregam muitos pedaços individuais de materiais por toda a fábrica. O jogo começou com um motor de correia altamente otimizado? Não! De fato, era quase impossível otimizar o design original do cinto - meio que fazia uma simulação física dos itens no cinto, o que criava algumas coisas interessantes que você poderia fazer (é assim que você obtém uma jogabilidade "emergente" - jogabilidade que surpreende o designer), mas significava que era preciso simular todos os itens do cinto. Com milhares de correias, você obtém dezenas de milhares de itens simulados fisicamente - até mesmo removê-los e deixar que as correias façam o trabalho permite reduzir o tempo de CPU associado em 95-99%, mesmo sem considerar coisas como localidade da memória. Mas só é útil fazer isso quando você realmente atinge esses limites.

Praticamente tudo o que tinha algo a ver com cintos precisava ser refeito para permitir que os cintos fossem otimizados. E os cintos precisavam ser otimizados, porque você precisava de muitos cintos para uma grande fábrica, e grandes fábricas são uma atração do jogo. Afinal, se você não pode ter grandes fábricas, por que ter um mundo infinito? Engraçado que você deve perguntar - as versões iniciais não o fizeram :) O jogo foi reformulado e remodelado várias vezes para chegar onde está agora - incluindo um remake 100% inicial quando eles perceberam que o Java não é o caminho a percorrer. jogo como este e mudou para C ++. E funcionou muito bem para o Factorio (embora ainda fosse bom não ter sido otimizado desde o início - especialmente porque esse era um projeto de hobby, que poderia simplesmente ter falhado por falta de interesse).

Mas o problema é que existemmuitas coisas que você pode fazer com uma fábrica de escopo limitado - e muitos jogos mostraram exatamente isso. Os limites podem ser ainda mais poderosos para se divertir do que as liberdades; Spacechem seria mais divertido se os "mapas" fossem infinitos? Se você começasse com "cintos" altamente otimizados, seria praticamente forçado a seguir por esse caminho; e você não poderia explorar outras direções do projeto (como ver as coisas interessantes que você pode fazer com as correias transportadoras simuladas pela física). Você está limitando seu potencial espaço de design. Pode não parecer assim porque você não vê muitos jogos inacabados, mas a parte difícil é fazer a diversão certa - para cada jogo divertido que você vê, provavelmente há centenas que simplesmente não conseguiram chegar lá e foram descartadas (ou pior, lançado como uma bagunça horrível). Se a otimização o ajudar a fazer isso - vá em frente. Se isso não acontecer ... provavelmente é prematuro. Se você acha que alguma mecânica de jogo funciona muito bem, mas precisa de otimizações para realmente brilhar - vá em frente. Se você não tem mecânica interessante,não os otimize . Encontre a diversão primeiro - você descobrirá que a maioria das otimizações não ajuda nisso e geralmente é prejudicial.

Finalmente, você tem um jogo ótimo e divertido. Faz sentido otimizar agora ? Ha! Ainda não está tão claro quanto você imagina. Existe algo divertidovocê pode fazer? Não esqueça que seu tempo ainda é limitado. Tudo exige um esforço, e você deseja concentrar esse esforço no local mais importante. Sim, mesmo se você estiver criando um "jogo grátis" ou um jogo "de código aberto". Veja como o jogo é jogado; observe onde o desempenho se torna um gargalo. A otimização desses locais torna mais divertido (como construir fábricas cada vez maiores e cada vez mais emaranhadas)? Permite atrair mais jogadores (por exemplo, com computadores mais fracos ou em plataformas diferentes)? Você sempre precisa priorizar - procure a relação esforço / rendimento. Você provavelmente encontrará muitas frutas simples apenas jogando seu jogo e vendo outras pessoas jogando o jogo. Mas observe a parte importante - para chegar lá, você precisa de um jogo . Concentre-se nisso.

Como uma cereja no topo, considere que a otimização nunca termina. Não é uma tarefa com uma pequena marca de seleção que você conclui e passa para outras tarefas. Sempre há "mais uma otimização" que você pode fazer, e grande parte de qualquer desenvolvimento é entender as prioridades. Você não faz otimização por causa da otimização - você faz isso para atingir um objetivo específico (por exemplo, "200 unidades na tela ao mesmo tempo em um Pentium de 333 MHz" é um ótimo objetivo). Não perca o objetivo da meta do terminal apenas porque se concentra demais nas metas intermediárias que talvez nem sejam pré-requisitos para a meta do terminal.

Luaan
fonte
Acredito que os desenvolvedores da fatorio agora estão otimizando os cintos novamente para que precisem simular itens periodicamente e, em todos os outros momentos, apenas interpola a posição deles quando você os olha. Mas eles só estão fazendo isso agora, quando o jogo inteiro se baseia efetivamente em cintos (e robôs) há um longo tempo e não até que as pessoas comecem a perceber e reclamar do desempenho.
immibis 24/05
2
@immibis Exatamente. Fazer com que funcione melhor antes que as pessoas alcancem os limites seria um desperdício de recursos, que são mais bem gastos em outros lugares. Mesmo com as otimizações mais recentes, é provável que você encontre o problema apenas para as megafábricas muito além do escopo da jogabilidade normal (lançando o foguete). Mas habilitou as novas configurações de jogabilidade do Marathon, que exigem uma logística muito mais pesada. E, novamente, eles identificaram os gargalos obtendo estatísticas das pessoas que realmente jogavam o jogo - cerca de metade dos gargalos foi uma grande surpresa para eles.
Luaan
@Attie Então você entende a pergunta, mas não a resposta? Você está apenas tentando dizer que uma resposta mais simples é possível (por exemplo, "Economia")? Não vejo como seu comentário deve ser construtivo.
Luaan 24/05
2
@Fattie Se você acha que a resposta é "Otimizar apenas quando você souber qual é o gargalo", publique-a como resposta. Você já gastou muito mais palavras do que seria necessário para obter uma boa resposta, então vá em frente. Que tipo de otimização não torna as coisas melhores e piores? Eu certamente nunca vi um. O fundamental que continuo reiterando é que sempre é uma troca - um tempo que poderia ser melhor gasto em outro lugar, complexidade que limita sua flexibilidade ... Os exemplos apontam que "cedo" não é um termo absoluto; algumas otimizações são necessárias desde o primeiro dia, enquanto outras são prejudiciais.
Luaan
1
@Fattie "Otimizar apenas quando você souber qual é o gargalo" é a resposta para "Quando devo otimizar?" e não é uma resposta para "Por que é tão ruim otimizar muito cedo?".
immibis 24/05
10

Muitas das respostas parecem focar muito no aspecto de desempenho da "otimização", enquanto eu próprio gosto de analisar a otimização e toda a provação de otimizar muito cedo em um nível mais abstrato.

Me humor quando tento elaborar minha perspectiva com a ajuda de poliomanos.

Suponha que tenhamos alguns limites fixos definidos pela estrutura ou mecanismo com o qual estamos trabalhando.

insira a descrição da imagem aqui

Em seguida, criamos nossa primeira camada / módulo do jogo dessa maneira.

insira a descrição da imagem aqui

Seguindo em frente, construímos nossa segunda camada / módulo.

insira a descrição da imagem aqui

Nesse ponto, podemos notar que há algum espaço disponível entre os dois módulos e podemos ficar tentados a otimizá-lo para fazer pleno uso dos limites que nos são atribuídos.

insira a descrição da imagem aqui

Perfeito, agora o aplicativo está utilizando totalmente os recursos disponíveis, o aplicativo está melhor, certo?

Continuamos construindo a terceira camada / módulo de nosso aplicativo e, de repente, chegamos à conclusão (talvez até uma que não poderíamos ter previsto durante o planejamento inicial) de que a terceira camada / módulo não está funcionando para nós.

Buscamos uma alternativa, encontramos uma e, no final, isso também exige que alteremos o 2º módulo do nosso aplicativo, pois é incompatível com o nosso 3º módulo recém-selecionado. (Felizmente, é um pouco compatível com o nosso primeiro módulo, para que não tenhamos que reescrever tudo do zero.)

Então juntamos tudo ...

insira a descrição da imagem aqui

Hmm, você pode ver o que aconteceu?

Ao otimizar muito cedo, agora tornamos as coisas piores em termos de eficiência, uma vez que o que otimizamos não foi o que acabamos finalizando.

E se desejarmos adicionar alguns módulos adicionais ou petiscos extras posteriormente, talvez não tenhamos mais capacidade para fazê-los neste momento.

insira a descrição da imagem aqui

E ajustar o nível mais baixo do nosso sistema não é mais tão viável, pois ele foi enterrado sob todas as outras camadas.

Se, no entanto, tivéssemos optado por esperar com otimização imediata, teríamos algo assim:

insira a descrição da imagem aqui

E agora, se realizarmos a otimização neste ponto, obteremos algo satisfatório para analisar.

insira a descrição da imagem aqui

Espero que pelo menos alguns de vocês tenham se divertido tanto lendo isso quanto me diverti fazendo isso :) e se agora sentem que têm uma melhor compreensão do assunto - tanto melhor.

Geco do teto
fonte
3
Estou removendo o anúncio. Não nos importamos como você criou as imagens; a única parte importante é a suposição de que você tem o direito de publicá-las.
Gnemlock
2
@Gnemlock é justo o suficiente, embora minha intenção não tenha sido direcionada para a publicidade, também posso ver como ela pode ser facilmente mal interpretada como tal, e concordarei que ela não adiciona nada à resposta, por isso não tem lugar nela.
Gecko de teto
2
Eu acho que essa metáfora visual é extremamente eficaz. Ótima abordagem para responder à pergunta! :)
DMGregory
8

Dinheiro.

Tudo se resume a isso. Dinheiro. Como tempo é dinheiro *, mais tempo você gasta em atividades que não garantem gerar mais dinheiro (ou seja, você não pode considerar essas atividades como investimentos ), mais dinheiro corre o risco de desperdiçar e menos dinheiro ganhará com o jogo.

Aqui estão alguns mais potenciais efeitos colaterais de otimização muito cedo, e razões por que você deve evitá-lo:

  • Seus colegas o odeiam porque você brinca na sua caixa de areia com seus brinquedos enquanto eles trabalham duro para obter recursos.
  • Os atrasos desagradam a alta gerência e fazem com que a equipe perca as datas de entrega, o que faz com que o jogo seja lançado na primavera e não antes da temporada de festas, o que, por sua vez, faz com que o jogo não venda o suficiente. E por causa disso, a sede decide desligar o estúdio, efetivamente deixando você sem emprego. E nenhum trabalho não significa dinheiro. (E como ninguém gosta de você porque você gastou muito tempo otimizando antecipadamente, ninguém quer escrever uma resenha positiva sobre seu trabalho para você no LinkedIn, o que o manterá sem dinheiro por mais tempo.)

* Outras respostas destacaram bastante a parte 'time'


Como observação lateral, geralmente , a experiência ajuda a determinar o que é e o que não é a otimização prematura, o que tem valor para os negócios e o que não tem.

Por exemplo, se você trabalhou no jogo A e percebeu, no final do projeto, que o recurso XYZ era particularmente pesado no seu loop do jogo, e você começou a trabalhar no jogo B, que tem exatamente o mesmo recurso, decidindo reescrever o recurso e otimizá-lo desde o início não é realmente uma otimização prematura, pois você sabe que será um gargalo se nada for feito.

Alexandre Vaillancourt
fonte
1
@JBentley esse é um ponto válido. No entanto, há uma resposta conseqüente ao "porquê" como implicação direta da experiência adquirida. Portanto, 1) otimizar antecipadamente pode levar a perda de tempo em partes do código que não afetam o tempo de execução médio (dada a experiência, você deve saber quais construções de código são candidatas às melhores práticas de otimização) 2) otimizar antecipadamente pode dificultar o código manter devido às restrições de design impostas a algum sistema. Assim, você pode ganhar muito pouco em troca de ciclos de desenvolvimento mais longos / código complicado de manter.
Teodron # 22/17
3
@JBentley Considere isso como um adendo à resposta do DMGregory.
Alexandre Vaillancourt
1
Essa parece ser a única resposta que vale a pena aqui. A questão é uma tautologia. Você também pode perguntar "Então, por que, na verdade, você quer que um jogo ganhe mais do que menos dinheiro nas lojas de aplicativos?" Como você pode responder isso?
Fattie
@Fattie É bom ter esse tipo de perspectiva em uma resposta. Mas dizer que é a única resposta que vale a pena é restringir o escopo da pergunta de maneira irracional. Você está assumindo, por exemplo, que os únicos jogos feitos são dentro de organizações com fins lucrativos (claramente não é verdade). Você também está assumindo que é óbvio para o OP que a otimização antecipada leva a mais tempo gasto (e, portanto, mais dinheiro se estivermos falando de um negócio). Na verdade, a pergunta do OP mostra que ele não tem certeza disso.
JBentley
6

A otimização é, por definição, o processo de aumentar a eficiência de uma solução até o ponto em que ela perde sua eficácia. Esse processo implica, então, uma redução do espaço da solução.

No estágio inicial do desenvolvimento de software, ainda pode haver "requisitos ocultos": se você reduzir muito o espaço da sua solução, poderá acabar em uma situação em que não conseguirá satisfazer um "requisito oculto" quando ele aparecer. em um estágio posterior de desenvolvimento, obrigando você a modificar a arquitetura, adicionando, dessa maneira, instabilidade e um possível grupo de comportamentos indesejados.

A idéia é, então, colocar toda a solução em funcionamento e somente então, quando todos os requisitos forem corrigidos e implementados, aperte o código. Você verá, então, que muitas otimizações que você teria implementado de uma só vez ao codificar não são mais viáveis ​​devido à interação com os requisitos atrasados.

O espaço real da solução é sempre maior do que o esperado no início, porque não podemos ter um conhecimento perfeito de um sistema muito complexo.

Faça funcionar primeiro. Depois aperte as cordas.

Earendel
fonte
2
Eu acho que "eficácia" não é a palavra que você procura - significa "o poder de produzir um efeito"; portanto, em certo sentido, otimização tem a ver com aumentar a eficácia. Você está buscando alguma versão específica de eficácia? (Você poderia criar um link para ele?) Você quer dizer algo como flexibilidade?
Doppelgreener
2
@doppelgreener Isso significa que você fazer a solução mais eficiente até que ele pára de produzir os efeitos que deseja ...
immibis
1
"Faça funcionar primeiro. Depois aperte as cordas." - Isso tem que valer tanto quanto o resto da resposta combinada. E para não dizer que as outras coisas não eram boas.
Ebyrob 25/05
1
@ebyrob: existe outro ditado: "Faça funcionar, faça certo, faça rápido" wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
ninjalj
@ninjalj Essa também é boa. Obviamente, meu chefe sempre me diz: "Não se apresse, faça as coisas certas". Portanto, não sei se o que você está dizendo é tão universal quanto o principal sobre o qual o pôster original está solicitando mais detalhes.
Ebyrob 29/05
2

Em resumo, muitas vezes a otimização precoce se torna um esforço desperdiçado, se você quiser mudar as coisas mais tarde, e então você otimizou as estruturas de código facilmente alteráveis ​​para algo muito mais baixo, e agora você precisa alterá-lo novamente para um nível alto. abordagem de nível e otimizar o resultado mais uma vez.

Esse é um erro comum para desenvolvedores iniciantes que gostam de se concentrar em "ter feito um trabalho útil", como otimização, sem pensar se é o momento certo para fazê-lo. À medida que você ganha experiência na programação de grandes projetos, como jogos, você aprenderá quando é necessário otimizar a base de código existente e quando for muito cedo. Não tenha medo de cometer esse erro, você só se beneficiará aprendendo com ele.

Em geral, otimize apenas se você realmente não puder trabalhar com a construção de desenvolvimento neste momento. Como se você estivesse criando e excluindo milhões de objetos 60 vezes por segundo.

Então, eu diria que é bom, em termos de experiência de aprendizado, otimizar algumas vezes mais cedo: p

user1306322
fonte
2

A otimização se concentra em fazer o computador funcionar melhor no código, enquanto o desenvolvimento exige que o programador funcione melhor no código.

A otimização não traz insights. Faz o computador trabalhar menos e o programador mais.

Obviamente, existem categorias diferentes, como projetar um carro. Não há nada de errado em otimizar um mecanismo antes de construir seu primeiro chassi, mas otimizar o chassi antes que você não saiba o formato do motor pode acabar perdendo tempo. A modularidade e as especificações podem obter vários locais viáveis ​​para o trabalho de otimização, mesmo antes da montagem do produto como um todo.

user101307
fonte
Isso seria melhorado, levando-se a algo além de definir otimização e falando sobre a situação prática no desenvolvimento de jogos, em vez de procurar analogias sobre carros.
Doppelgreener
A resposta perfeita para uma pergunta tautológica.
Fattie
2

Discordo totalmente de todas essas afirmações de que o código não deve ser otimizado nos estágios iniciais. Depende de qual estágio você está. Se esse é o protótipo do MVP e você está apenas tentando ver o jogo que leva de 1 a 2 semanas, você está pronto para reescrever tudo o que já escreveu. Sim, otimização não importa. Mas se você já está trabalhando em um jogo que você sabe que será lançado, ele deve ter otimizado o código o tempo todo. Os contos que as pessoas dizem sobre novos recursos e coisas que podem ser adicionados são conceitos errôneos. É uma arquitetura de código mal projetada, em vez de código otimizado. Você não precisa reescrever nada para adicionar novos elementos se tiver um bom design de código.

Por exemplo, se você tiver um algoritmo A * pathfinding, não seria melhor escrevê-lo da melhor maneira possível? Em vez de alterar a metade do código mais tarde, você precisou fazer algumas alterações no algoritmo, porque agora ele precisa de outro método de chamadas e retornos de chamada? E se você já possui um código otimizado desde o início, economizará muito tempo. Como você pode estabelecer relações entre objetos e como eles interagem, em vez de criar cegamente toneladas de novos scripts e estabelecer todas as conexões imediatamente - isso leva a um código sphagetti pouco claro.

A última coisa que quero acrescentar é que, mais tarde, mesmo que você não queira mais fazer o jogo, você terá seu algoritmo A * otimizado que poderá usar nos próximos jogos. Reutilização. Por exemplo, muitos jogos têm inventário, busca de caminhos, geração de procedimentos, interações entre npc, sistema de combate, IA, interação com interface, gerenciamento de entradas. Agora você deve se perguntar: "Quantas vezes eu reescrevi esses elementos do zero?" Eles são 70-80% do jogo. Você realmente precisa reescrevê-los o tempo todo? E se eles não forem otimizados?

Lua Espontânea _Max_
fonte
5
Se o código A * deve começar otimizado, como otimizado é "otimizado"? Você codifica manualmente as coisas na montagem antes de fazer benchmarks? Penso que o trabalho geralmente não trivial sobre micro-otimizações deve ser deixado até que os benchmarks tenham sido feitos. O compilador de otimização pode fazer um trabalho melhor do que você nas micro-otimizações e é muito mais barato.
gmatht
@gmatht Bem, o A * pode ser diferente, como eu disse, pode ter um design ruim e não funcionar muito bem. Mas pode ser multithread, com base na pilha de Fibonacci, ter algumas previsões, divididas em partes. Se estamos falando de busca de caminhos, podemos adicionar o campo de fluxo a ser misturado com A *. Também alisamento, multi altura. Talvez A * não seja o melhor exemplo, mas, como eu disse, otimizações não têm nada a ver com a alteração do código, o desenvolvedor está alterando o código porque ele foi mal projetado, por exemplo, ele não seguiu o SOLID por 1 motivo. escreveu este A * bem, você não precisa mais escrever.
Candid Moon _Max_
As micro otimizações @gmatht não são realmente o problema. Eu acho que OP significa mais da melhor e mais eficiente maneira de calcular o comportamento da IA, shader ou qualquer outra coisa.
Candid Moon _Max_
Se você sabe escrever essas coisas com eficiência, não vejo problema em fazê-lo. Levará a mesma quantidade de tempo ou até menos. Depende mais da experiência de um desenvolvedor. Como programador, se eu souber uma solução melhor, não escreveria uma porcaria. Se eu souber escrever heap Fibonacci, não usarei List e classificação para obter os valores mínimo ou máximo. É claro que levará mais tempo e esforço para escrevê-lo, em vez de usar List.Sort (); , mas e se eu já tiver uma reutilizável?
Candid Moon _Max_
2
@CandidMoon Não concordo com "Levará a mesma quantidade de tempo ou até menos". escrever código eficiente. Talvez levasse o mesmo tempo para escrever código que não é obviamente ineficiente, mas quando sua noção de "eficiência" significa considerar a contenção de cache, adicionar multithreading, usar intrínsecas específicas da CPU disponíveis em apenas algumas CPUs, alternar algoritmos para N grande devido a O (N log N) vs O (N) - esse tipo de coisa é difícil e propensa a erros e leva muito mais tempo do que a implementação simples. Você só faz isso quando sabe que isso afetará materialmente o resultado final.
Michael Anderson