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?
performance
optimization
Sr. Matt
fonte
fonte
Respostas:
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.
fonte
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:
É 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:
Você pode alterar os mecanismos em cada módulo sem reestruturar toda a base de código quando precisar otimizar.
fonte
IRiverCrossing
e, quando ficar claro que o volume de tráfego é muito grande para a balsa configurada, implemente a ponteIRiverCrossing
e 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."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.
fonte
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.
Em seguida, criamos nossa primeira camada / módulo do jogo dessa maneira.
Seguindo em frente, construímos nossa segunda camada / módulo.
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.
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 ...
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.
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:
E agora, se realizarmos a otimização neste ponto, obteremos algo satisfatório para analisar.
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.
fonte
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:
* 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.
fonte
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.
fonte
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
fonte
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.
fonte
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?
fonte