Estou otimizando prematuramente?

9

Atualmente, estou no estágio de design de uma arquitetura baseada em componentes em C ++.

Meu design atual inclui o uso de recursos como:

  • std::vectors de std::shared_ptrs para segurar os componentes
  • std::dynamic_pointer_cast
  • std::unordered_map<std::string,[yada]>

Os componentes representam dados e lógica de vários itens necessários em um software semelhante a um jogo, como Gráficos, Física, IA, Áudio, etc.

Eu já li em todo lugar que as falhas de cache são difíceis de executar, por isso executei alguns testes, o que me levou a acreditar que, de fato, ele pode atrasar um aplicativo.

Não pude testar os recursos de linguagem mencionados anteriormente, mas é dito em muitos lugares que eles tendem a custar muito e devem ser evitados, se possível.

Como estou no estágio de design da arquitetura, e estes serão incluídos no núcleo do design, devo tentar encontrar maneiras de evitá-los agora, já que será muito difícil alterá-lo mais tarde, se houver desempenho problemas?

Ou sou pego fazendo otimização prematura?

Vaillancourt
fonte
3
Eu ficaria muito relutante em optar por um design que tornasse muito difícil mudar posteriormente, independentemente dos problemas de desempenho. Evite isso se puder. Existem muitos projetos flexíveis e rápidos.
2741616
11
Mesmo sem conhecer os detalhes, a resposta a essa pergunta é quase sempre um retumbante "SIM !!".
Mawg diz que restabelece Monica
2
@Mawg "... No entanto, não devemos desperdiçar nossas oportunidades nesses 3% críticos." Como esse é o núcleo do design, como eu poderia saber se estou trabalhando nesses 3%?
Vaillancourt
11
Um excelente ponto, Alexandre (+1), e, sim, eu sei que a última metade da citação, que quase nunca é mencionada :-) Mas, voltando ao meu comentário antes disso (o que é refletido na resposta aceita) , the answer to this question is almost always a resounding "YES !!". Eu ainda acho que é melhor fazê-lo funcionar primeiro e otimizar depois, mas YMMV, todos têm sua opinião, todos válidos, e somente o OP pode realmente responder à sua própria pergunta - subjetiva.
Mawg diz que restabelece Monica
11
@AlexandreVaillancourt Continue lendo o artigo de Knuth (PDF, a citação vem do lado direito da página rotulada 268, página 8 em um leitor de PDF). "... ele será sábio ao examinar atentamente o código crítico; mas somente após a identificação do código. Muitas vezes é um erro fazer julgamentos a priori sobre quais partes de um programa são realmente críticas, uma vez que a experiência universal de programadores que usam ferramentas de medição têm demonstrado que suas suposições intuitivas falham ". (ênfase dele)
8bittree

Respostas:

26

Sem ler nada além do título: Sim.

Depois de ler o texto: Sim. Embora seja verdade que mapas e ponteiros compartilhados etc. não tenham um bom desempenho em cache, você certamente descobrirá que o que deseja usá-los - até onde eu entendo - não é o gargalo e não será mantido ou use o cache de forma eficiente, independentemente da estrutura de dados.

Escreva o software evitando os erros mais estúpidos, depois teste, encontre os gargalos e otimize!

Fwiw: https://xkcd.com/1691/

Steffen
fonte
3
Acordado. Faça com que funcione corretamente primeiro, porque não importa a rapidez com que não funcione. E lembre-se sempre de que as otimizações mais eficazes não envolvem ajustes no código, elas envolvem encontrar um algoritmo diferente e mais eficiente.
21316 Todd Knarr
10
Gostaria de salientar que a primeira linha não é verdadeira porque a otimização é sempre prematura, mas sim porque a otimização não é prematura apenas se você souber que precisa, e nesse caso você não perguntaria a respeito. Portanto, a primeira linha é verdadeira apenas porque o fato de você estar fazendo uma pergunta sobre se a otimização é ou não prematura significa que você não tem certeza de que precisa de otimização, o que, por definição, a torna prematura. Ufa.
Jörg W Mittag
@ JörgWMittag: concordou.
steffen 27/07
3

Eu não estou familiarizado com C ++, mas em geral depende.

Você não precisa otimizar prematuramente os algoritmos isolados, onde é possível otimizar facilmente quando se trata disso.

No entanto, você precisa obter o design geral do aplicativo para alcançar os principais indicadores de desempenho desejados.

Por exemplo, se você precisar projetar um aplicativo para atender a milhões de solicitações por segundo, precisará pensar na escalabilidade do aplicativo ao projetá-lo, em vez de fazê-lo funcionar.

Pelicano-voador baixo
fonte
3

Se você precisar perguntar, sim. Otimização prematura significa otimização antes de ter certeza de que há um problema de desempenho significativo.

JacquesB
fonte
1

ECS? Na verdade, sugiro que talvez não seja prematuro pensar muito no lado orientado a dados do design e comparar diferentes representantes, pois isso pode afetar seus designs de interface , e é muito caro mudar tarde no final. o jogo. Além disso, o ECS exige muito trabalho e pensamento antecipado, e acho que vale a pena utilizar parte desse tempo para garantir que isso não traga luto por desempenho no nível de design mais adiante, considerando como ele estará no coração de sua empresa. mecanismo todo enlouquecido. Esta parte me brilha:

unordered_map<string,[yada]>

Mesmo com otimizações de string pequenas, você tem um contêiner de tamanho variável (strings) dentro de outro contêiner de tamanho variável (unordered_maps). Na verdade, as pequenas otimizações cordas realmente pode ser tão prejudicial quanto útil, neste caso, se a tabela é muito escassa, uma vez que a pequena otimização corda implicaria que cada índice não utilizado da tabela de hash ainda vai usar mais memória para a otimização SS ( sizeof(string)seria ser muito maior) até o ponto em que a sobrecarga total de memória da sua tabela de hash pode custar mais do que o que você está armazenando nela, especialmente se for um componente simples como um componente de posição, além de gerar mais falhas de cache com grande avanço para passar de uma entrada na tabela de hash para a próxima.

Estou assumindo que a string é algum tipo de chave, como uma identificação de componente. Nesse caso, isso já torna as coisas dramaticamente mais baratas:

unordered_map<int,[yada]>

... se você quiser os benefícios de poder ter nomes fáceis de usar que os scripts podem usar, por exemplo, as seqüências internas podem oferecer o melhor dos dois mundos aqui.

Dito isto, se você pode mapear a string para um intervalo razoavelmente baixo de índices densamente usados, poderá fazer isso:

vector<[yada]> // the index and key become one and the same

A razão pela qual não considero prematuro é porque, novamente, isso pode afetar seus designs de interface. O objetivo do DOD não deve ser o de tentar apresentar as representações de dados mais eficientes imagináveis ​​de uma só vez na IMO (que geralmente devem ser alcançadas iterativamente, conforme necessário), mas pensar nelas o suficiente para projetar interfaces no topo para trabalhar com isso. dados que deixam espaço suficiente para o perfil e a otimização sem alterações de design em cascata.

Como um exemplo ingênuo, um software de processamento de vídeo que combina todo o seu código com isso:

// Abstract pixel that could be concretely represented by
// RGB, BGR, RGBA, BGRA, 1-bit channels, 8-bit channels, 
// 16-bit channels, 32-bit channels, grayscale, monochrome, 
// etc. pixels.
class IPixel
{
public:
    virtual ~IPixel() {}
    ...
};

Não vai chegar muito longe sem uma reescrita potencialmente épica, já que a ideia de abstrair no nível de pixel único já é extremamente ineficiente (a vptrprópria custaria muitas vezes mais memória que o pixel inteiro) em comparação com a abstração no nível da imagem (o que geralmente representam milhões de pixels). Então, pense bastante em suas representações de dados com antecedência, para que você não precise enfrentar um cenário de pesadelo e, idealmente, não mais, mas aqui acho que vale a pena pensar sobre essas coisas desde que você não queira criar um mecanismo complexo em torno do seu ECS e descubra que o próprio ECS é o gargalo de maneiras que exigem que você mude as coisas no nível do design.

Quanto às falhas de cache do ECS, na minha opinião, os desenvolvedores geralmente se esforçam demais para tornar seus ECS compatíveis com o cache. Começa a render muito pouco esforço para tentar acessar todos os seus componentes de maneira perfeitamente contígua e geralmente implica em copiar e embaralhar dados em todo o lugar. Geralmente, é bom o suficiente, digamos, apenas modificar índices de componentes de classificação antes de acessá-los, para que você os acesse de uma maneira em que pelo menos não esteja carregando uma região de memória em uma linha de cache, apenas para despejá-la e carregá-la. tudo de novo no mesmo loop apenas para acessar uma parte diferente da mesma linha de cache. E um ECS não precisa fornecer eficiência incrível em todos os aspectos. Não é como se um sistema de entrada se beneficiasse tanto quanto um sistema de física ou renderização, então eu recomendo apontar para "bom" eficiência global e "excelente" apenas nos locais onde você realmente precisa. Dito isto, o uso deunordered_mape stringaqui são fáceis o suficiente para evitar.


fonte